Admin analytics: heatmap per device, anteprima staff e analisi costi.
Separa heatmap mobile/desktop, opt-out analytics per operatori, dashboard costi con KPI e trend mensili. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,10 +35,40 @@ RSpec.describe "Admin analytics", type: :request do
|
||||
expect(response.body).to include("40")
|
||||
end
|
||||
|
||||
it "mostra la heatmap di una pagina" do
|
||||
it "mostra la heatmap di una pagina con tab dispositivo" do
|
||||
get admin_analytics_page_path, params: { page_path: "/prezzi" }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include("admin-heatmap")
|
||||
expect(response.body).to include("/prezzi")
|
||||
expect(response.body).to include("is-active")
|
||||
expect(response.body).to include(I18n.t("admin.analytics.devices.desktop"))
|
||||
end
|
||||
|
||||
it "mostra solo i punti del dispositivo selezionato" do
|
||||
AnalyticsPageCell.create!(
|
||||
day: Time.zone.today,
|
||||
page_path: "/prezzi",
|
||||
device: "mobile",
|
||||
cell_x: 5,
|
||||
cell_y: 8,
|
||||
click_count: 9,
|
||||
move_count: 0
|
||||
)
|
||||
|
||||
get admin_analytics_page_path, params: { page_path: "/prezzi", device: "mobile", layer: "click" }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include("x":5")
|
||||
expect(response.body).to include("c":9")
|
||||
expect(response.body).not_to include("x":10")
|
||||
end
|
||||
|
||||
it "attiva e disattiva l'anteprima staff" do
|
||||
post admin_analytics_preview_path, params: { return_to: "/" }
|
||||
expect(response).to redirect_to("/")
|
||||
expect(response.cookies[Analytics::Suppress::COOKIE_NAME]).to eq("1")
|
||||
|
||||
delete admin_analytics_preview_path
|
||||
expect(response).to redirect_to(admin_analytics_path)
|
||||
expect(response.cookies[Analytics::Suppress::COOKIE_NAME]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Admin costs", type: :request do
|
||||
let!(:admin) { AdminAccount.create!(username: "ops-costs", password: "Password123") }
|
||||
let(:month) { Time.zone.today.beginning_of_month }
|
||||
|
||||
before do
|
||||
post admin_login_path, params: { username: admin.username, password: "Password123" }
|
||||
PlatformCostEntry.create!(month: month, label: "Piattaforma", amount_cents: 8_500)
|
||||
end
|
||||
|
||||
it "mostra la dashboard costi" do
|
||||
get admin_costs_path, params: { month: month.strftime("%Y-%m") }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include(I18n.t("admin.costs.index.title"))
|
||||
expect(response.body).to include("85.00")
|
||||
end
|
||||
|
||||
it "crea, aggiorna ed elimina una voce di costo" do
|
||||
post admin_cost_entries_path,
|
||||
params: {
|
||||
platform_cost_entry: {
|
||||
month: month.strftime("%Y-%m"),
|
||||
label: "Stream CPX",
|
||||
amount_euros: "42.50",
|
||||
notes: "overflow nodes"
|
||||
}
|
||||
}
|
||||
expect(response).to redirect_to(admin_costs_path(month: month.strftime("%Y-%m")))
|
||||
entry = PlatformCostEntry.find_by(label: "Stream CPX")
|
||||
expect(entry.amount_cents).to eq(4_250)
|
||||
|
||||
patch admin_cost_entry_path(entry),
|
||||
params: { platform_cost_entry: { month: month.strftime("%Y-%m"), label: "Stream CPX", amount_euros: "50" } }
|
||||
expect(response).to redirect_to(admin_costs_path(month: month.strftime("%Y-%m")))
|
||||
expect(entry.reload.amount_cents).to eq(5_000)
|
||||
|
||||
delete admin_cost_entry_path(entry)
|
||||
expect(response).to redirect_to(admin_costs_path(month: month.strftime("%Y-%m")))
|
||||
expect(PlatformCostEntry.find_by(id: entry.id)).to be_nil
|
||||
end
|
||||
end
|
||||
@@ -52,6 +52,24 @@ RSpec.describe "Analytics ingest", type: :request do
|
||||
expect(AnalyticsPageCell.where(page_path: "/clubs/:id").sum(:move_count)).to eq(12)
|
||||
end
|
||||
|
||||
it "ignora eventi con cookie anteprima staff" do
|
||||
expect do
|
||||
post "/analytics/events",
|
||||
params: {
|
||||
events: [
|
||||
{ type: "pageview", path: "/prezzi-suppress", device: "desktop", ts: Time.current.to_i * 1000 }
|
||||
]
|
||||
},
|
||||
headers: { "Cookie" => "#{Analytics::Suppress::COOKIE_NAME}=1" },
|
||||
as: :json
|
||||
end.not_to change { AnalyticsPageStat.where(page_path: "/prezzi-suppress").count }
|
||||
|
||||
expect(response).to have_http_status(:accepted)
|
||||
body = JSON.parse(response.body)
|
||||
expect(body["accepted"]).to eq(0)
|
||||
expect(body["suppressed"]).to eq(true)
|
||||
end
|
||||
|
||||
it "rifiuta path esclusi" do
|
||||
post "/analytics/events",
|
||||
params: {
|
||||
|
||||
@@ -29,16 +29,36 @@ RSpec.describe "Analytics snapshots", type: :request do
|
||||
end
|
||||
|
||||
it "rifiuta path esclusi" do
|
||||
post "/analytics/snapshot",
|
||||
params: {
|
||||
path: "/admin/analytics",
|
||||
device: "desktop",
|
||||
width: 1440,
|
||||
height: 900,
|
||||
image: jpeg_upload
|
||||
}
|
||||
expect do
|
||||
post "/analytics/snapshot",
|
||||
params: {
|
||||
path: "/admin/analytics",
|
||||
device: "desktop",
|
||||
width: 1440,
|
||||
height: 900,
|
||||
image: jpeg_upload
|
||||
}
|
||||
end.not_to change(AnalyticsPageSnapshot, :count)
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
expect(AnalyticsPageSnapshot.count).to eq(0)
|
||||
end
|
||||
|
||||
it "salta snapshot con cookie anteprima staff" do
|
||||
expect do
|
||||
post "/analytics/snapshot",
|
||||
params: {
|
||||
path: "/prezzi-suppress",
|
||||
device: "desktop",
|
||||
width: 1440,
|
||||
height: 2200,
|
||||
image: jpeg_upload
|
||||
},
|
||||
headers: { "Cookie" => "#{Analytics::Suppress::COOKIE_NAME}=1" }
|
||||
end.not_to change(AnalyticsPageSnapshot, :count)
|
||||
|
||||
expect(response).to have_http_status(:accepted)
|
||||
body = JSON.parse(response.body)
|
||||
expect(body["skipped"]).to eq(true)
|
||||
expect(body["suppressed"]).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Admin::CostAnalytics do
|
||||
let!(:coach) do
|
||||
User.create!(email: "cost-coach@test.it", name: "Coach", password: "Password123", role: "coach")
|
||||
end
|
||||
let!(:club) { Club.create!(name: "Tigers Club", sport: "volleyball") }
|
||||
let!(:team) { club.teams.create!(name: "Tigers U16", sport_key: "pallavolo") }
|
||||
let!(:match) { team.matches.create!(opponent_name: "Rival", sport_key: "pallavolo") }
|
||||
let(:month) { Date.new(2024, 3, 1) }
|
||||
|
||||
before do
|
||||
PlatformCostEntry.delete_all
|
||||
PlatformCostEntry.create!(
|
||||
month: month,
|
||||
label: "Hetzner",
|
||||
amount_cents: 10_000
|
||||
)
|
||||
StreamSession.create!(
|
||||
match: match,
|
||||
user: coach,
|
||||
platform: "matchlivetv",
|
||||
status: "ended",
|
||||
started_at: month + 1.day + 10.hours,
|
||||
ended_at: month + 1.day + 11.hours,
|
||||
total_duration_secs: 3600
|
||||
)
|
||||
Billing::Payment.create!(
|
||||
club: club,
|
||||
amount_cents: 5_900,
|
||||
currency: "EUR",
|
||||
status: "paid",
|
||||
provider: "stripe",
|
||||
paid_at: month + 2.days
|
||||
)
|
||||
end
|
||||
|
||||
it "calcola KPI e allocazione per società" do
|
||||
analytics = Admin::CostAnalytics.new(month: month)
|
||||
summary = analytics.summary
|
||||
|
||||
expect(summary[:cost_cents]).to eq(10_000)
|
||||
expect(summary[:sessions]).to eq(1)
|
||||
expect(summary[:hours]).to eq(1.0)
|
||||
expect(summary[:revenue_cents]).to eq(5_900)
|
||||
expect(summary[:margin_cents]).to eq(-4_100)
|
||||
expect(summary[:cost_per_hour_cents]).to eq(10_000)
|
||||
|
||||
clubs = analytics.club_breakdown
|
||||
expect(clubs.size).to eq(1)
|
||||
expect(clubs.first[:club_name]).to eq("Tigers Club")
|
||||
expect(clubs.first[:allocated_cost_cents]).to eq(10_000)
|
||||
expect(clubs.first[:revenue_cents]).to eq(5_900)
|
||||
end
|
||||
|
||||
it "ricalcola dopo modifica delle voci di costo" do
|
||||
PlatformCostEntry.create!(month: month, label: "S3", amount_cents: 2_000)
|
||||
|
||||
summary = Admin::CostAnalytics.new(month: month).summary
|
||||
expect(summary[:cost_cents]).to eq(12_000)
|
||||
expect(summary[:cost_per_session_cents]).to eq(12_000)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user