PhoenixKitWebAnalytics.Migrations (PhoenixKitWebAnalytics v0.5.0)

Copy Markdown View Source

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 keys
  • 2 — 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 timelines
  • 3 — session_start on events (the first hit of each visit) with a partial index on inserted_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 run
  • 4 — engagement totals on daily_stats and the daily_dims table (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 detail
  • 5 — 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 plain session_id index 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 — see PhoenixKitWebAnalytics.Recordings
  • 7 — click_id and click_param on 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

Types

Migration options: :prefix and :version.

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

opts()

@type opts() :: keyword() | map()

Migration options: :prefix and :version.

Core passes a keyword list; the internal helpers normalize it to a map and pass that back through the same public functions, so both are accepted.

Functions

current_version()

@spec current_version() :: pos_integer()

The version this code expects the schema to be at.

down(opts \\ [])

@spec down(opts()) :: :ok

Roll back. Migration-context only.

migrated_version(opts \\ [])

@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.

migrated_version_runtime(opts \\ [])

@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).

up(opts \\ [])

@spec up(opts()) :: :ok

Run migrations up to (and including) the target version. Migration-context only.