feat: web interaction coverage beyond click + fix fabricated XHR traceparent (v0.1.13) - #2
Merged
Merged
Conversation
…eparent (v0.1.13) Auto-tap tracking listened only to `click`, which made search-and-filter UIs largely invisible: a keyboard-submitted search, a <select> change and a form submit all produced nothing. It now also emits `user_interaction` spans for `change`, `submit` (including Enter in a text entry, which React handlers routinely swallow) and `input` (debounced 500ms, one span per settled edit). The new `interactionEvents` option narrows the set; its values are exactly the `user_interaction.type` vocabulary, so a dashboard filter maps directly onto the config knob that controls it. Free text never leaves the page. `user_interaction.value` is set only for controls with a closed value space, sensitive fields emit no `input` span at all, and their description is reported as `redacted` rather than falling through to neighbouring text content. Separately, XMLHttpRequest was sending a `traceparent` built from fresh random ids rather than the emitted http.request span's, so XHR calls correlated with neither their own span nor the backend, and the backend saw a parent id it would never receive. `emitSpan` creates and ends a span in one call and so cannot serve a caller that needs ids before the work finishes; the new `Scout.startTrackedSpan()` fills that gap, applying the same beforeSend, sampling and view-counter bookkeeping. Moving fetch onto it too closed three more gaps: fetch was bypassing beforeSend and view.resource.count, a failed XHR emitted two spans because both `error` and `loadend` ran the finalizer, and XHR spans had a near-zero duration. Also documents that `headers` is read per export, so an expiring bearer token can be rotated by mutating the object passed to initialize — locked by tests so a future defensive copy cannot silently break it.
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.
Prerequisite for wiring scout-react into the logX Grafana app plugin, where the two gaps below would have been immediately visible: logX is a search-and-filter UI, and its config bootstrap uses a synchronous
XMLHttpRequest.Interaction coverage beyond
clickAuto-tap tracking listened to a single
clickevent, so a keyboard-submitted search, a<select>change and a form submit all produced nothing.Now also emitted, all as
user_interactionspans distinguished byuser_interaction.type:change<select>, checkbox, radio, file, date, time, range. Free-text inputs excluded — theirchangefires on blur, reporting an edit somewhere the user does not associate with it.submitsubmitevent;user_interaction.triggertells the two apart.inputNew
interactionEventsoption narrows the set. Its values are theuser_interaction.typevocabulary, so a dashboard filter maps straight onto the config knob controlling it — no mapping table.[]disables interaction tracking without touchingenableAutoTapTracking.Free text never leaves the page
user_interaction.valueis set only for controls with a closed value space (selected option label,checked/unchecked).password/email/tel/hiddenfields emit noinputspan at all, and their description reports asredactedrather than falling through to neighbouring text content — the redaction lives insidedescribeElement's fallthrough chain, so every event type inherits it.Fix:
XMLHttpRequestsent a fabricatedtraceparentThe header was built from fresh random ids rather than the emitted
http.requestspan's, so XHR calls correlated with neither their own span nor the backend, and the backend saw a parent span id it would never receive.emitSpancreates and ends a span in one call, so it structurally cannot serve a caller that needs the ids before the work finishes. AddedScout.startTrackedSpan(), which applies the samebeforeSend, sampling and view-counter bookkeeping but hands back a handle the caller ends later. It returnsnullwhen the span is sampled out or dropped — and callers must respect that, or they inject atraceparentnaming a parent that will never be exported.Moving
fetchonto the same helper closed three further gaps:fetchspans were bypassingbeforeSendand were not counted inview.resource.count; only XHR was.http.requestspans — both theerrorandloadendlisteners ran the finalizer.XHR spans also gained the
http.provider.*classification that onlyfetchhad.Mutable headers contract
Documented and test-locked that
headersis read per export rather than snapshotted, so an expiring bearer token is rotated by mutating the object passed toinitialize— no re-init, no dropped batches. Two tests guard the reference chain (config.test.tsfor resolution,otlp-exporter.test.tsfor export), because a defensive copy anywhere along it would strand every exporter on the token the page loaded with.Verification
make cigreen: fmt-check, lint, typecheck, 210 tests (24 files), build, publint + are-the-types-wrong across all four export subpaths.New coverage: 5 debounce/ordering/redaction cases for text edits, 6 for change/submit/keyboard, 2 for
interactionEventsnarrowing, and a full XHR suite including the traceparent-matches-span assertion that reproduced the bug.Note on tagging
v0.1.12sits on the merge commit onmain, so this needs merging beforev0.1.13is tagged — the tag is what triggers the npm publish.