Aggiunge heatmaps first-party (click/scroll) con pagina admin Analytics.
Raccoglie eventi aggregati dietro consenso cookie, senza PII né tracking su admin/regia/live/replay; GA4 resta invariato. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Analytics
|
||||
class Aggregate
|
||||
BATCH = 500
|
||||
|
||||
def call
|
||||
loop do
|
||||
ids = AnalyticsEvent.order(:occurred_at).limit(BATCH).pluck(:id)
|
||||
break if ids.empty?
|
||||
|
||||
events = AnalyticsEvent.where(id: ids).to_a
|
||||
apply_clicks(events.select { |e| e.event_type == "click" })
|
||||
apply_stats(events.reject { |e| e.event_type == "click" })
|
||||
AnalyticsEvent.where(id: ids).delete_all
|
||||
end
|
||||
end
|
||||
|
||||
def purge_old!(retention: 30.days)
|
||||
# Raw should already be empty after aggregate; keep as safety net.
|
||||
AnalyticsEvent.where("occurred_at < ?", retention.ago).delete_all
|
||||
AnalyticsPageCell.where("day < ?", retention.ago.to_date).delete_all
|
||||
AnalyticsPageStat.where("day < ?", retention.ago.to_date).delete_all
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def apply_clicks(events)
|
||||
return if events.empty?
|
||||
|
||||
grid = AnalyticsPageCell::GRID_SIZE
|
||||
grouped = Hash.new(0)
|
||||
events.each do |e|
|
||||
next if e.x_pct.nil? || e.y_pct.nil?
|
||||
|
||||
cell_x = [[(e.x_pct.to_f / 100 * grid).floor, grid - 1].min, 0].max
|
||||
cell_y = [[(e.y_pct.to_f / 100 * grid).floor, grid - 1].min, 0].max
|
||||
key = [e.occurred_at.in_time_zone.to_date, e.page_path, e.device, cell_x, cell_y]
|
||||
grouped[key] += 1
|
||||
end
|
||||
|
||||
now = Time.current
|
||||
grouped.each do |(day, page_path, device, cell_x, cell_y), count|
|
||||
cell = AnalyticsPageCell.find_or_initialize_by(
|
||||
day: day, page_path: page_path, device: device, cell_x: cell_x, cell_y: cell_y
|
||||
)
|
||||
cell.click_count = cell.click_count.to_i + count
|
||||
cell.created_at ||= now
|
||||
cell.updated_at = now
|
||||
cell.save!
|
||||
end
|
||||
end
|
||||
|
||||
def apply_stats(events)
|
||||
return if events.empty?
|
||||
|
||||
grouped = {}
|
||||
events.each do |e|
|
||||
day = e.occurred_at.in_time_zone.to_date
|
||||
key = [day, e.page_path, e.device]
|
||||
bucket = grouped[key] ||= { pageviews: 0, scroll_samples: 0, scroll_sum: 0, max_scroll: 0 }
|
||||
case e.event_type
|
||||
when "pageview"
|
||||
bucket[:pageviews] += 1
|
||||
when "scroll"
|
||||
pct = e.scroll_pct.to_f.round
|
||||
bucket[:scroll_samples] += 1
|
||||
bucket[:scroll_sum] += pct
|
||||
bucket[:max_scroll] = [bucket[:max_scroll], pct].max
|
||||
end
|
||||
end
|
||||
|
||||
now = Time.current
|
||||
grouped.each do |(day, page_path, device), vals|
|
||||
stat = AnalyticsPageStat.find_or_initialize_by(day: day, page_path: page_path, device: device)
|
||||
stat.pageview_count = stat.pageview_count.to_i + vals[:pageviews]
|
||||
stat.scroll_samples = stat.scroll_samples.to_i + vals[:scroll_samples]
|
||||
stat.scroll_sum_pct = stat.scroll_sum_pct.to_i + vals[:scroll_sum]
|
||||
stat.max_scroll_pct = [stat.max_scroll_pct.to_i, vals[:max_scroll]].max
|
||||
stat.created_at ||= now
|
||||
stat.updated_at = now
|
||||
stat.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user