All checks were successful
deploy / deploy (push) Successful in 1m37s
Event call sites (product_view, add_to_cart, checkout_start, purchase) were only passing visitor_hash and pathname, leaving browser, OS, screen size and country nil. Add AnalyticsHook.attrs/1 helper to extract common analytics fields from socket assigns, and use it in all LiveView event call sites. Checkout controller reads the same fields from the session. Also fix plug analytics test to clear stale events before assertions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
115 lines
4.2 KiB
Elixir
115 lines
4.2 KiB
Elixir
defmodule BerrypodWeb.AnalyticsHook do
|
|
@moduledoc """
|
|
LiveView on_mount hook for analytics — Layer 2 of the progressive pipeline.
|
|
|
|
Reads analytics data from the session (set by the Plugs.Analytics plug) and
|
|
tracks subsequent SPA navigations via handle_params. Skips the initial mount
|
|
since the plug already recorded that pageview.
|
|
|
|
Also handles the `analytics:screen` event from the JS hook (Layer 3) to
|
|
capture screen width for device classification.
|
|
"""
|
|
|
|
import Phoenix.Component, only: [assign: 3]
|
|
import Phoenix.LiveView, only: [attach_hook: 4, connected?: 1]
|
|
|
|
alias Berrypod.Analytics
|
|
|
|
def on_mount(:track, _params, session, socket) do
|
|
socket =
|
|
socket
|
|
|> assign(:analytics_visitor_hash, session["analytics_visitor_hash"])
|
|
|> assign(:analytics_browser, session["analytics_browser"])
|
|
|> assign(:analytics_os, session["analytics_os"])
|
|
|> assign(:analytics_referrer, session["analytics_referrer"])
|
|
|> assign(:analytics_referrer_source, session["analytics_referrer_source"])
|
|
|> assign(:analytics_utm_source, session["analytics_utm_source"])
|
|
|> assign(:analytics_utm_medium, session["analytics_utm_medium"])
|
|
|> assign(:analytics_utm_campaign, session["analytics_utm_campaign"])
|
|
|> assign(:analytics_screen_size, session["analytics_screen_size"])
|
|
|> assign(:analytics_country_code, session["country_code"])
|
|
|> assign(:analytics_initial_mount, true)
|
|
|
|
socket =
|
|
if connected?(socket) and socket.assigns.analytics_visitor_hash do
|
|
socket
|
|
|> attach_hook(:analytics_params, :handle_params, &handle_analytics_params/3)
|
|
|> attach_hook(:analytics_events, :handle_event, &handle_analytics_event/3)
|
|
else
|
|
socket
|
|
end
|
|
|
|
{:cont, socket}
|
|
end
|
|
|
|
defp handle_analytics_params(_params, uri, socket) do
|
|
# Skip the initial mount — the plug already recorded this pageview
|
|
if socket.assigns.analytics_initial_mount do
|
|
{:cont, assign(socket, :analytics_initial_mount, false)}
|
|
else
|
|
# Skip if admin user is browsing
|
|
if admin?(socket) do
|
|
{:cont, socket}
|
|
else
|
|
pathname = URI.parse(uri).path
|
|
|
|
Analytics.track_pageview(%{
|
|
pathname: pathname,
|
|
visitor_hash: socket.assigns.analytics_visitor_hash,
|
|
referrer: socket.assigns.analytics_referrer,
|
|
referrer_source: socket.assigns.analytics_referrer_source,
|
|
utm_source: socket.assigns.analytics_utm_source,
|
|
utm_medium: socket.assigns.analytics_utm_medium,
|
|
utm_campaign: socket.assigns.analytics_utm_campaign,
|
|
country_code: socket.assigns.analytics_country_code,
|
|
screen_size: socket.assigns.analytics_screen_size,
|
|
browser: socket.assigns.analytics_browser,
|
|
os: socket.assigns.analytics_os
|
|
})
|
|
|
|
# Clear referrer/UTMs after first SPA navigation — they only apply to the entry
|
|
{:cont,
|
|
socket
|
|
|> assign(:analytics_referrer, nil)
|
|
|> assign(:analytics_referrer_source, nil)
|
|
|> assign(:analytics_utm_source, nil)
|
|
|> assign(:analytics_utm_medium, nil)
|
|
|> assign(:analytics_utm_campaign, nil)}
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Extracts common analytics attributes from socket assigns.
|
|
Call sites can merge their own fields (pathname, revenue, etc.) on top.
|
|
"""
|
|
def attrs(socket) do
|
|
%{
|
|
visitor_hash: socket.assigns[:analytics_visitor_hash],
|
|
browser: socket.assigns[:analytics_browser],
|
|
os: socket.assigns[:analytics_os],
|
|
screen_size: socket.assigns[:analytics_screen_size],
|
|
country_code: socket.assigns[:analytics_country_code]
|
|
}
|
|
end
|
|
|
|
defp handle_analytics_event("analytics:screen", %{"width" => width}, socket)
|
|
when is_integer(width) do
|
|
screen_size = classify_screen(width)
|
|
{:cont, assign(socket, :analytics_screen_size, screen_size)}
|
|
end
|
|
|
|
defp handle_analytics_event(_event, _params, socket), do: {:cont, socket}
|
|
|
|
defp classify_screen(width) when width < 768, do: "mobile"
|
|
defp classify_screen(width) when width < 1024, do: "tablet"
|
|
defp classify_screen(_width), do: "desktop"
|
|
|
|
defp admin?(socket) do
|
|
case socket.assigns[:current_scope] do
|
|
%{user: %{}} -> true
|
|
_ -> false
|
|
end
|
|
end
|
|
end
|