Module: Controllers::AnalyticsEvents

Extended by:
ActiveSupport::Concern
Included in:
ApplicationController
Defined in:
app/concerns/controllers/analytics_events.rb

Overview

Server-side analytics event tracking that bridges to client-side Analytics.js

This concern allows controllers to queue analytics events that will be
fired client-side on the next page load (including after redirects).

Events are stored in session[SESSION_KEY] and delivered via the
globals.json response. The client-side primeval.js handler fires them
through the Analytics service.

Why session and not flash?
We previously stored events in flash[:analytics_events]. flash is meant
for short user-facing strings and is auto-rendered by toast/flash partials,
so structured analytics payloads (Arrays of Hashes) crashed those
partials and could be silently dropped if the user made a non-globals.json
request between the redirect and the next page load. Session storage is
durable across multiple intermediate requests and is consumed (drained)
exactly once by GlobalsController#show.

Supported Events:
ALL VENDORS (GA4, Google Ads, Facebook, Bing):
- 'Cart - Product Added'
- 'Cart - Order Completed'
- 'Lead - Form Submitted'
- 'Instant Quote - Completed FH'
- 'Instant Quote - Completed SM'

GA4 ONLY (funnel analysis):
- 'Cart - Viewed'
- 'Cart - Customer Info Entered'
- 'Cart - Shipping Info Added'
- 'Cart - Payment Info Entered'
- 'My Account - Quote Viewed'

Usage:

In controller action:

track_event('Cart - Product Added', { quoteId: @room.id, value: @room.total })
redirect_to cart_path

See: doc/features/ANALYTICS_TRACKING_EVENTS.MD for full documentation

Constant Summary collapse

SESSION_KEY =
'queued_analytics_events'
MAX_QUEUED_EVENTS =

Hard cap to keep the session cookie small if a controller misbehaves and
never triggers a globals.json drain (e.g. headless bot traffic).

25

Instance Method Summary collapse

Instance Method Details

#consume_queued_analytics_eventsArray<Hash> (protected)

Pop and clear all queued events. Intended for GlobalsController#show.

Returns:

  • (Array<Hash>)

    queued events, or nil when none are queued.



78
79
80
81
# File 'app/concerns/controllers/analytics_events.rb', line 78

def consume_queued_analytics_events
  events = session.delete(SESSION_KEY)
  events.presence
end

#track_event(event_name, properties = {}) ⇒ Object (protected)

Queue an analytics event to be fired client-side via globals.json

Parameters:

  • event_name (String)

    Segment-compatible event name (e.g., 'Product Added', 'Lead Submitted')

  • properties (Hash) (defaults to: {})

    Event properties to pass to Analytics.track()



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'app/concerns/controllers/analytics_events.rb', line 55

def track_event(event_name, properties = {})
  # Drop server-queued conversion events when an employee is masquerading as
  # a customer ("Login as this user"). Every track_event call (orders,
  # leads, instant quotes) flows through here, so this single guard blocks
  # GA4/Google Ads/FB/Bing pushes at the source. Client-side Analytics.js
  # has its own short-circuit via the heatwave:masquerade meta tag, but we
  # don't even queue the event so the customer's Analytics history stays
  # untouched.
  if respond_to?(:account_impersonated?) && 
    Rails.logger.info "[track_event] skip '#{event_name}' because masquerade session active"
    return
  end

  queue = session[SESSION_KEY] || []
  queue << {
    'event' => event_name,
    'properties' => properties.deep_stringify_keys
  }
  session[SESSION_KEY] = queue.last(MAX_QUEUED_EVENTS)
end