Skip to content

feat: submission analytics — drop-off, completion rate, median time #137

@harshtandiya

Description

@harshtandiya

Context

No visibility into form funnel today. Need metrics per form:

  • Completion rate — submitted / engaged sessions
  • Drop-off rate — per-field abandonment
  • Median time to submit
  • View count vs engaged (first interaction)

Proposed approach

Data model

New doctype FP Submission Session (one row per visit):

field purpose
form (Link) parent form
session_id (Data, unique) uuid from sessionStorage
user (Link, nullable) logged-in user
guest_hash (Data) hashed IP+UA for guest dedupe
started_at (Datetime) first interaction
last_activity_at (Datetime) rolling
submitted_at (Datetime, nullable) finalize
submission (Dynamic Link, nullable) resulting doc
last_field_reached (Data) last focused field
engaged (Check) first focus fired
abandoned (Check) cron-marked after stale
user_agent, referrer (Data) context

Optional follow-up doctype FP Submission Event (append-only) — session, event_type (focus/blur/submit), field, timestamp. Enables fine-grained drop-off later. Out of scope for first cut.

Lifecycle

  1. Page open — frontend generates session_id, stores in sessionStorage. No backend call yet.
  2. First field focus — POST /api/method/forms_pro.api.metrics.start_session → creates row with engaged=true.
  3. Field blur — throttled beacon updates last_field_reached, last_activity_at. Batch every 5s or on blur.
  4. Submit — existing submit_form_response sets submitted_at, links submission.
  5. Tab close / navigationvisibilitychangenavigator.sendBeacon() final flush.
  6. Stale sweeper — daily cron marks sessions with last_activity_at > 30min and no submitted_at as abandoned=true.

API

New module forms_pro/api/metrics.py:

  • start_session(form, session_id)allow_guest=True, idempotent
  • update_session(session_id, last_field, ...) — accepts beacon blob
  • get_form_metrics(form) — aggregate query for dashboard

Frontend

  • New composable useSubmissionMetrics(formId) in frontend/src/composables/
  • Wire into SubmissionPage.vue and PublicEdit.vue (skip edit mode for metrics)
  • visibilitychange listener fires sendBeacon with current state
  • Display metrics on ManageForm.vue overview

Metric queries

  • Completion rate = count(submitted_at) / count(engaged=true)
  • Median time = percentile_cont(0.5) within group (order by submitted_at - started_at)
  • Drop-off per field = count(last_field_reached=X AND submitted_at IS NULL) / count(reached X)

Edge cases

  • Guests — opaque session_id, no user link
  • Refresh mid-fillsessionStorage persists session_id; resume row
  • Edit mode — skip logging entirely
  • Multi-tab — separate session_ids, accepted
  • Botsengaged=false filters out view-only noise
  • Beacon drops (~5%) — cron sweeper backstops
  • Write amplification — throttle blur events, no per-keystroke writes
  • PII — log field names only, never values

Out of scope

  • Per-field event log (FP Submission Event) — follow-up
  • External analytics integration (PostHog)
  • A/B testing form variants
  • Geo / device breakdown

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions