Upgrade heatmaps stile Hotjar: movimenti mouse e anteprima pagina live.
Traccia i movimenti aggregati, overlay a gradienti sull’iframe della pagina e toggle click/move in admin. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,14 +10,14 @@ module Analytics
|
||||
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" })
|
||||
apply_points(events.select { |e| e.event_type == "click" }, :click_count)
|
||||
apply_points(events.select { |e| e.event_type == "move" }, :move_count)
|
||||
apply_stats(events.select { |e| %w[pageview scroll].include?(e.event_type) })
|
||||
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
|
||||
@@ -25,7 +25,7 @@ module Analytics
|
||||
|
||||
private
|
||||
|
||||
def apply_clicks(events)
|
||||
def apply_points(events, counter_attr)
|
||||
return if events.empty?
|
||||
|
||||
grid = AnalyticsPageCell::GRID_SIZE
|
||||
@@ -36,7 +36,7 @@ module Analytics
|
||||
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
|
||||
grouped[key] += e.weight.to_i.clamp(1, 500)
|
||||
end
|
||||
|
||||
now = Time.current
|
||||
@@ -44,7 +44,7 @@ module Analytics
|
||||
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[counter_attr] = cell[counter_attr].to_i + count
|
||||
cell.created_at ||= now
|
||||
cell.updated_at = now
|
||||
cell.save!
|
||||
@@ -61,7 +61,7 @@ module Analytics
|
||||
bucket = grouped[key] ||= { pageviews: 0, scroll_samples: 0, scroll_sum: 0, max_scroll: 0 }
|
||||
case e.event_type
|
||||
when "pageview"
|
||||
bucket[:pageviews] += 1
|
||||
bucket[:pageviews] += e.weight.to_i.clamp(1, 500)
|
||||
when "scroll"
|
||||
pct = e.scroll_pct.to_f.round
|
||||
bucket[:scroll_samples] += 1
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
module Analytics
|
||||
class Ingest
|
||||
MAX_BATCH = 50
|
||||
RATE_LIMIT_PER_MINUTE = 60
|
||||
MAX_BATCH = 80
|
||||
RATE_LIMIT_PER_MINUTE = 90
|
||||
|
||||
Result = Struct.new(:accepted, :rejected, :rate_limited, keyword_init: true)
|
||||
|
||||
@@ -62,11 +62,17 @@ module Analytics
|
||||
return nil if page_path.length > 200
|
||||
|
||||
occurred_at = parse_time(data[:ts] || data[:occurred_at]) || Time.current
|
||||
weight = data[:n].presence || data[:weight].presence || 1
|
||||
weight = Integer(weight)
|
||||
weight = 1 if weight < 1
|
||||
weight = 500 if weight > 500
|
||||
|
||||
attrs = {
|
||||
page_path: page_path,
|
||||
device: device,
|
||||
event_type: event_type,
|
||||
occurred_at: occurred_at,
|
||||
weight: weight,
|
||||
x_pct: nil,
|
||||
y_pct: nil,
|
||||
scroll_pct: nil,
|
||||
@@ -75,7 +81,7 @@ module Analytics
|
||||
}
|
||||
|
||||
case event_type
|
||||
when "click"
|
||||
when "click", "move"
|
||||
x = clamp_pct(data[:x] || data[:x_pct])
|
||||
y = clamp_pct(data[:y] || data[:y_pct])
|
||||
return nil if x.nil? || y.nil?
|
||||
@@ -92,6 +98,8 @@ module Analytics
|
||||
end
|
||||
|
||||
attrs
|
||||
rescue ArgumentError, TypeError
|
||||
nil
|
||||
end
|
||||
|
||||
def clamp_pct(value)
|
||||
|
||||
Reference in New Issue
Block a user