Usa screenshot di pagina come sfondo heatmap al posto dell’iframe.

Cattura periodica client-side (con consenso), storage ActiveStorage e overlay allineato all’immagine.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-20 23:14:47 +02:00
co-authored by Cursor
parent 406a90ae5b
commit 70fccc493e
25 changed files with 442 additions and 73 deletions
@@ -75,7 +75,7 @@ module Admin
@max_scroll = stats.maximum(:max_scroll_pct).to_i
@avg_scroll = @scroll_samples.positive? ? (@scroll_sum.to_f / @scroll_samples).round : 0
@grid = AnalyticsPageCell::GRID_SIZE
@preview_url = preview_url_for(@page_path)
@snapshot = find_snapshot(@page_path, @filters[:device])
end
private
@@ -88,11 +88,14 @@ module Admin
nil
end
def preview_url_for(path)
return nil if path.blank?
return nil if path.include?(":")
def find_snapshot(page_path, device)
scope = AnalyticsPageSnapshot.where(page_path: page_path)
if device.present? && AnalyticsEvent::DEVICES.include?(device)
snap = scope.find_by(device: device)
return snap if snap&.image&.attached?
end
"#{MatchLiveTv.app_public_url.chomp('/')}#{path}"
scope.order(captured_at: :desc).detect { |s| s.image.attached? }
end
end
end
@@ -0,0 +1,21 @@
# frozen_string_literal: true
module Analytics
class SnapshotsController < ActionController::API
def create
result = Analytics::SnapshotIngest.new(
path: params[:path] || params[:page_path],
device: params[:device],
width: params[:width],
height: params[:height],
image: params[:image],
remote_ip: request.remote_ip
).call
return head :too_many_requests if result.rate_limited
return render json: { ok: false, error: result.error }, status: :unprocessable_content unless result.ok
render json: { ok: true, skipped: result.skipped }, status: :accepted
end
end
end