Versioned migration coordinator for phoenix_kit_web_analytics — the module
returned from PhoenixKitWebAnalytics.migration_module/0.
mix phoenix_kit.update discovers this, compares migrated_version_runtime/1
(what's installed) with current_version/0 (what the code needs), and — when
behind — generates a host migration whose up/0 calls up/1 here. So the
host installs/updates this module's tables with no hand-written migration,
and it honors the host's --prefix (named-schema installs).
Version is tracked via a COMMENT ON TABLE on
phoenix_kit_web_analytics_events (mirroring core's own
PhoenixKit.Migrations.Postgres and PhoenixKitBoards.Migrations) — not
just a boolean "does the table exist", so a future V2 can tell "not
installed" apart from "installed at V1".
Versions:
0— tables absent (not installed)1—phoenix_kit_web_analytics_events+phoenix_kit_web_analytics_daily_stats, UUIDv7 primary keys2— engagement columns on events (engaged_ms,scroll_depth,target) for the"interaction"and"leave"event types, and a(session_id, inserted_at)index for session timelines3—session_starton events (the first hit of each visit) with a partial index oninserted_at, so the visits list pages through visit starts instead of grouping every event in the period. Existing rows are marked afterwards, in batches, by the retention pass (PhoenixKitWebAnalytics.Retention) — not here, where one statement over a large table would hold the migration's lock for its whole run4— engagement totals ondaily_statsand thedaily_dimstable (one row per day, site and breakdown value), so every report reads finished days from rollups; the rollup watermark is reset so the days still in raw events are rolled up again with the new detail5— index changes for the paths that must not grow with the table:(path, inserted_at)for page drill-downs and(user_uuid, inserted_at)for a user's visits; the plainsession_idindex goes ((session_id, inserted_at)already serves it)6—phoenix_kit_web_analytics_recordings: optional session recordings (pointer movement, clicks, hovers, scrolling), in chunks per page view — seePhoenixKitWebAnalytics.Recordings7—click_idandclick_paramon events: an ad platform's click identifier and which parameter carried it (gclid,gbraid, …, so an offline conversion upload knows its kind), with partial indexes on each (only a visit that lands with a click identifier carries one)
Large existing tables
Creating an index blocks writes to its table while it builds, and inside a
migration it can't be built CONCURRENTLY. On a busy install with a large
events table, create V3's, V5's and V7's event indexes by hand first, under
the same names — the migration then finds them and skips the build:
CREATE INDEX CONCURRENTLY phoenix_kit_web_analytics_events_session_starts_index
ON phoenix_kit_web_analytics_events (inserted_at) WHERE session_start;
CREATE INDEX CONCURRENTLY phoenix_kit_web_analytics_events_path_inserted_at_index
ON phoenix_kit_web_analytics_events (path, inserted_at);
CREATE INDEX CONCURRENTLY phoenix_kit_web_analytics_events_user_uuid_inserted_at_index
ON phoenix_kit_web_analytics_events (user_uuid, inserted_at)
WHERE user_uuid IS NOT NULL;
CREATE INDEX CONCURRENTLY phoenix_kit_web_analytics_events_click_id_index
ON phoenix_kit_web_analytics_events (click_id)
WHERE click_id IS NOT NULL;
CREATE INDEX CONCURRENTLY phoenix_kit_web_analytics_events_click_param_inserted_at_index
ON phoenix_kit_web_analytics_events (click_param, inserted_at)
WHERE click_param IS NOT NULL;(session_start must exist before the first; add it with ALTER TABLE … ADD COLUMN session_start boolean NOT NULL DEFAULT false, which is
instant. Likewise click_id varchar(255) and click_param varchar(20),
both nullable, before the last two.)
Prefix safety
Every statement passes prefix: through, index names are bare (Postgres
scopes an index to its table's schema), and existence checks are anchored to
the target schema — so a --prefix "analytics" install lands entirely
inside that schema.
Summary
Functions
The version this code expects the schema to be at.
Roll back. Migration-context only.
The version currently installed in the database (0 if the table is absent).
Migration-context only — reads via Ecto.Migration.repo/0.
Runtime-safe version of migrated_version/1 — uses PhoenixKit's configured
repo instead of the Ecto.Migration repo() helper, so it can be called
from Mix tasks and other non-migration contexts (mix phoenix_kit.update).
Run migrations up to (and including) the target version. Migration-context only.
Types
Functions
@spec current_version() :: pos_integer()
The version this code expects the schema to be at.
@spec down(opts()) :: :ok
Roll back. Migration-context only.
@spec migrated_version(opts()) :: non_neg_integer()
The version currently installed in the database (0 if the table is absent).
Migration-context only — reads via Ecto.Migration.repo/0.
@spec migrated_version_runtime(opts()) :: non_neg_integer()
Runtime-safe version of migrated_version/1 — uses PhoenixKit's configured
repo instead of the Ecto.Migration repo() helper, so it can be called
from Mix tasks and other non-migration contexts (mix phoenix_kit.update).
@spec up(opts()) :: :ok
Run migrations up to (and including) the target version. Migration-context only.