mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 06:24:48 +08:00
This PR adds two KPI tiles to the Traffic section — bounce rate and average session duration — measured per browsing session as real engaged time: the seconds a tab is both visible and focused. It builds on the per-session engagement core already captures (`browser_pageview_session_engagements`), so the only new table is its daily summary `browser_pageview_session_engagement_daily_rollups` — additive, no existing schema changes. Key technical changes: 1. Reuse core's capture. Engaged time is already recorded per session by core's `human-activity-tracker` → `/srv/se` beacon → `browser_pageview_session_engagements`; this PR adds no second tracker, endpoint, table, or site setting. 2. Key engagement capture on `persist_browser_pageview_events` alone. `Middleware::RequestTracker#is_engagement_tracking_request?` and the `discourse-engagement-tracking-enabled` meta tag no longer also require the `dashboard_improvements` upcoming change, so engagement is captured wherever pageview events are persisted rather than only where the redesigned dashboard is enabled. 3. Pre-compute in a scheduled job. The existing `Jobs::MaintainBrowserPageviewRollups` job, which already maintains the country and referrer rollups on the same cadence and setting gate, now also rolls sessions into `browser_pageview_session_engagement_daily_rollups`, one row per `(date, logged_in)`, so the dashboard reads this small summary instead of scanning raw events. A session is bounced when it has a single pageview and under ten engaged seconds (no engagement counts as zero). Sessions that started within the last ten minutes are held out of aggregation until a later run, so a live session is not counted as a zero-engagement bounce before the client's first engagement flush (three minutes in) has arrived. Engagement rolls up only from the first engagement row's date forward, because pageview rows predate this feature and have no engagement data — counting them would make all of history look like instant bounces. 4. Show the tiles only when there's data. They render when `persist_browser_pageview_events` is on, alongside the existing Direct traffic KPI, with a neutral placeholder until the first visits are recorded; duration is formatted as `Xm Ys`.
87 lines
3.1 KiB
Ruby
Vendored
87 lines
3.1 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
class BrowserPageviewSessionEngagementDailyRollup < ActiveRecord::Base
|
|
BOUNCE_ENGAGED_SECONDS_THRESHOLD = 10
|
|
MIN_SESSION_AGE = 10.minutes
|
|
private_constant :BOUNCE_ENGAGED_SECONDS_THRESHOLD, :MIN_SESSION_AGE
|
|
|
|
def self.aggregate(start_date:, end_date:, source: BrowserPageviewEvent.rollup_source)
|
|
start_date = start_date.to_date
|
|
end_date = end_date.to_date + 1
|
|
|
|
transaction do
|
|
DB.exec(<<~SQL, start_date:, end_date:, source:)
|
|
DELETE FROM browser_pageview_session_engagement_daily_rollups rollup
|
|
WHERE rollup.date >= :start_date
|
|
AND rollup.date < :end_date
|
|
AND EXISTS (
|
|
SELECT 1
|
|
FROM browser_pageview_events
|
|
WHERE created_at >= rollup.date
|
|
AND created_at < rollup.date + 1
|
|
AND source = :source
|
|
)
|
|
SQL
|
|
|
|
DB.exec(
|
|
<<~SQL,
|
|
WITH active_sessions AS (
|
|
SELECT DISTINCT session_id
|
|
FROM browser_pageview_events
|
|
WHERE created_at >= :start_date
|
|
AND created_at < LEAST(:end_date::timestamp, :session_started_before::timestamp)
|
|
AND source = :source
|
|
),
|
|
session_pageviews AS (
|
|
SELECT
|
|
bpe.session_id,
|
|
MIN(bpe.created_at)::date AS date,
|
|
COUNT(*) AS pageview_count,
|
|
bool_or(bpe.user_id IS NOT NULL) AS logged_in
|
|
FROM browser_pageview_events bpe
|
|
JOIN active_sessions ON active_sessions.session_id = bpe.session_id
|
|
WHERE bpe.source = :source
|
|
GROUP BY bpe.session_id
|
|
HAVING MIN(bpe.created_at) >= :start_date
|
|
)
|
|
INSERT INTO browser_pageview_session_engagement_daily_rollups
|
|
(date, logged_in, sessions, bounced, engaged_seconds_total)
|
|
SELECT
|
|
session_pageviews.date,
|
|
session_pageviews.logged_in,
|
|
COUNT(*) AS sessions,
|
|
COUNT(*) FILTER (
|
|
WHERE session_pageviews.pageview_count = 1
|
|
AND COALESCE(engagement.engaged_seconds, 0) < :bounce_threshold
|
|
) AS bounced,
|
|
COALESCE(SUM(engagement.engaged_seconds), 0) AS engaged_seconds_total
|
|
FROM session_pageviews
|
|
LEFT JOIN browser_pageview_session_engagements engagement
|
|
ON engagement.session_id = session_pageviews.session_id
|
|
GROUP BY session_pageviews.date, session_pageviews.logged_in
|
|
SQL
|
|
start_date:,
|
|
end_date:,
|
|
session_started_before: MIN_SESSION_AGE.ago,
|
|
bounce_threshold: BOUNCE_ENGAGED_SECONDS_THRESHOLD,
|
|
source:,
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: browser_pageview_session_engagement_daily_rollups
|
|
#
|
|
# id :bigint not null, primary key
|
|
# bounced :bigint not null
|
|
# date :date not null
|
|
# engaged_seconds_total :bigint not null
|
|
# logged_in :boolean not null
|
|
# sessions :bigint not null
|
|
#
|
|
# Indexes
|
|
#
|
|
# idx_bpse_rollups_date_logged_in_unique (date,logged_in) UNIQUE
|
|
#
|