Class: Sentiero::Analytics::FormAnalyzer
- Defined in:
- lib/sentiero/analytics/form_analyzer.rb
Overview
Cross-session form analysis built from interaction patterns: rrweb input events (incremental, type 3 / source 5) carry the touched node's id and timing but never values, so this works with maskAllInputs enabled.
Attribution is per page: each session's windows are merged, ordered by time, and split into page segments on Meta-href boundaries (the shared Analyzer#each_page_segment mechanism). A (session, page) unit "starts" a form when the segment contains input events, and is "completed" only when a __form_submit custom event — emitted by the recorder's capture-phase document submit listener (config.track_forms) — lands in the same segment at or after the first input. A session counts as completed when EVERY page it interacted on was submitted, so a genuine submit elsewhere (a later todo add) can no longer mask an abandonment (the signup form it walked away from).
CAPTURE-VERSION NOTE: submits are counted ONLY from __form_submit events. Windows recorded before that capture existed (or with track_forms off) carry none and intentionally report ZERO submits — falling back to counting bare Meta/navigation events would resurrect the "navigating away counts as submitting" defect (product review P1.4/D4: 100% shown where the funnel proved 50%).
Fields are keyed per (page URL, node id) so the same rrweb node id on two different pages (ids reset every full-page load) no longer conflates two unrelated fields. Per field it reports touch rate (fraction of interacting sessions), aggregate time-to-fill, and re-fill frequency, plus a drop-off table (the last field touched in each abandoned page segment).
Compute-on-read: a full scan of Store#each_session_events up to the store's limits.analytics_max_scan_sessions — no fact-extraction tables.
Per-segment math (input recognition, field accumulation, drop-off, submit detection, and session-level started/completed semantics) lives in FormCollector so PageReportAnalyzer can share it without duplication.
Constant Summary collapse
- FULL_SNAPSHOT =
rrweb EventType.FullSnapshot and NodeType.Element — used to read field identity (name/id/type) from the DOM snapshot for human field labels.
2- ELEMENT_NODE =
2- FORM_CONTROL_TAGS =
%w[input select textarea].freeze
Constants included from Events
Events::CUSTOM, Events::INCREMENTAL, Events::META, Events::SOURCE_INPUT, Events::SOURCE_MOUSE_INTERACTION, Events::SOURCE_SCROLL
Instance Attribute Summary
Attributes inherited from Analyzer
Instance Method Summary collapse
-
#analyze(limit: nil, since: nil, until_time: nil) ⇒ Object
Aggregates form interactions across sessions.
Methods inherited from Analyzer
Methods included from EntryAttribution
#earlier?, #first_meta_href, #same_origin?, #update_entry_candidate
Methods included from Bounded
#bounded_fetch, #bounded_increment
Methods included from Stats
#mean, #offset_ms, #percentile, #top_counts
Constructor Details
This class inherits a constructor from Sentiero::Analytics::Analyzer
Instance Method Details
#analyze(limit: nil, since: nil, until_time: nil) ⇒ Object
Aggregates form interactions across sessions. Returns per-field stats, the drop-off table, form-level completion rate, the raw submit count, and whether the scan was capped. since/until_time (epoch seconds) bound the scan at the store level.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/sentiero/analytics/form_analyzer.rb', line 54 def analyze(limit: nil, since: nil, until_time: nil) scan_cap = limit || store.limits.analytics_max_scan_sessions collector = FormCollector.new # unbounded: no per-URL field cap here sessions = merge_windows(scan_cap, since, until_time) sessions.each { |session_id, session| analyze_session(collector, session_id, session) } started = collector.started_count { sessions_with_form_interaction: started, sessions_completed: collector.completed_count, completion_rate: ratio(collector.completed_count, started), total_submits: collector.total_submits, fields: collector.summarize_fields(started, include_labels: true), drop_off_fields: collector.summarize_drop_off(include_labels: true), was_truncated: sessions.size >= scan_cap } end |