add privacy-first analytics with progressive event collection
All checks were successful
deploy / deploy (push) Successful in 3m20s
All checks were successful
deploy / deploy (push) Successful in 3m20s
Three-layer pipeline: Plug for all HTTP requests (no JS needed), LiveView hook for SPA navigations, JS hook for screen width. ETS-backed buffer batches writes to SQLite every 10s. Daily-rotating salt for visitor hashing. Includes admin dashboard with date ranges, visitor trends, top pages, sources, devices, and e-commerce conversion funnel. Oban cron for 12-month data retention. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
106
test/berrypod_web/live/admin/analytics_test.exs
Normal file
106
test/berrypod_web/live/admin/analytics_test.exs
Normal file
@@ -0,0 +1,106 @@
|
||||
defmodule BerrypodWeb.Admin.AnalyticsTest do
|
||||
use BerrypodWeb.ConnCase, async: false
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
import Berrypod.AccountsFixtures
|
||||
|
||||
alias Berrypod.Analytics.{Buffer, Event}
|
||||
alias Berrypod.Repo
|
||||
|
||||
setup do
|
||||
send(Buffer, :flush)
|
||||
:timer.sleep(50)
|
||||
|
||||
user = user_fixture()
|
||||
%{user: user}
|
||||
end
|
||||
|
||||
describe "unauthenticated" do
|
||||
test "redirects to login", %{conn: conn} do
|
||||
{:error, redirect} = live(conn, ~p"/admin/analytics")
|
||||
assert {:redirect, %{to: path}} = redirect
|
||||
assert path == ~p"/users/log-in"
|
||||
end
|
||||
end
|
||||
|
||||
describe "analytics dashboard" do
|
||||
setup %{conn: conn, user: user} do
|
||||
%{conn: log_in_user(conn, user)}
|
||||
end
|
||||
|
||||
test "renders the analytics page", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/admin/analytics")
|
||||
|
||||
assert html =~ "Analytics"
|
||||
assert html =~ "Unique visitors"
|
||||
assert html =~ "Total pageviews"
|
||||
assert html =~ "Bounce rate"
|
||||
assert html =~ "Visit duration"
|
||||
end
|
||||
|
||||
test "shows zero state with no data", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/admin/analytics")
|
||||
|
||||
assert html =~ "No data for this period"
|
||||
end
|
||||
|
||||
test "shows data when events exist", %{conn: conn} do
|
||||
now = DateTime.utc_now() |> DateTime.truncate(:second)
|
||||
|
||||
Repo.insert_all(Event, [
|
||||
[
|
||||
id: Ecto.UUID.generate(),
|
||||
name: "pageview",
|
||||
pathname: "/",
|
||||
visitor_hash: :crypto.strong_rand_bytes(8),
|
||||
session_hash: :crypto.strong_rand_bytes(8),
|
||||
browser: "Chrome",
|
||||
os: "macOS",
|
||||
inserted_at: now
|
||||
]
|
||||
])
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
||||
|
||||
assert has_element?(view, "rect")
|
||||
end
|
||||
|
||||
test "changes period", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
||||
|
||||
html = render_click(view, "change_period", %{"period" => "7d"})
|
||||
assert html =~ "Analytics"
|
||||
end
|
||||
|
||||
test "changes tab to sources", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
||||
|
||||
html = render_click(view, "change_tab", %{"tab" => "sources"})
|
||||
assert html =~ "Top sources"
|
||||
assert html =~ "Top referrers"
|
||||
end
|
||||
|
||||
test "changes tab to countries", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
||||
|
||||
html = render_click(view, "change_tab", %{"tab" => "countries"})
|
||||
assert html =~ "Countries"
|
||||
end
|
||||
|
||||
test "changes tab to devices", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
||||
|
||||
html = render_click(view, "change_tab", %{"tab" => "devices"})
|
||||
assert html =~ "Browsers"
|
||||
assert html =~ "Operating systems"
|
||||
assert html =~ "Screen sizes"
|
||||
end
|
||||
|
||||
test "changes tab to funnel", %{conn: conn} do
|
||||
{:ok, view, _html} = live(conn, ~p"/admin/analytics")
|
||||
|
||||
html = render_click(view, "change_tab", %{"tab" => "funnel"})
|
||||
assert html =~ "Conversion funnel"
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user