discourse/spec/lib/statistics_spec.rb
Osama Sayegh 10ae7ef44a
FEATURE: Add estimated number of global and EU visitors to the about page (#28382)
This commit implements 2 new metrics/stats in the /about page for the _estimated_ numbers of unique visitors from the EU and the rest of the world. This new feature is currently off by default, but it can be enabled by turning on the hidden `display_eu_visitor_stats` site settings via the rails console.

There are a number of assumptions that we're making here in order to estimate the number of unique visitors, specifically:

1. we're assuming that the average of page views per anonymous visitor is similar to the average number of page views that a logged-in visitor makes, and
2. we're assuming that the ratio of logged in visitors from the EU is similar to the ratio of anonymous visitors from the EU

Discourse keeps track of the number of both logged-in and anonymous page views, and also the number of unique logged-in visitors and where they're from. So with those numbers and the assumptions above, we can estimate the number of unique anonymous visitors from the EU and the rest of the world.

Internal topic: t/128480.
2024-08-21 00:03:42 +03:00

219 lines
9.0 KiB
Ruby

# frozen_string_literal: true
RSpec.describe Statistics do
def create_page_views_and_user_visit_records(date, users)
freeze_time(date - 50.minutes) do
2.times { ApplicationRequest.increment!(:page_view_anon_browser) }
ApplicationRequest.increment!(:page_view_logged_in_browser)
end
freeze_time(date - 3.days) do
ApplicationRequest.increment!(:page_view_anon_browser)
5.times { ApplicationRequest.increment!(:page_view_logged_in_browser) }
end
freeze_time(date - 6.days) do
3.times { ApplicationRequest.increment!(:page_view_anon_browser) }
4.times { ApplicationRequest.increment!(:page_view_logged_in_browser) }
end
freeze_time(date - 8.days) do
ApplicationRequest.increment!(:page_view_anon_browser)
ApplicationRequest.increment!(:page_view_logged_in_browser)
end
freeze_time(date - 15.days) do
4.times { ApplicationRequest.increment!(:page_view_anon_browser) }
3.times { ApplicationRequest.increment!(:page_view_logged_in_browser) }
end
freeze_time(date - 31.days) do
ApplicationRequest.increment!(:page_view_anon_browser)
ApplicationRequest.increment!(:page_view_logged_in_browser)
end
UserVisit.create!(user_id: users[0].id, visited_at: date - 50.minute)
UserVisit.create!(user_id: users[0].id, visited_at: date - 36.hours)
UserVisit.create!(user_id: users[1].id, visited_at: date - 2.day)
UserVisit.create!(user_id: users[0].id, visited_at: date - 4.days)
UserVisit.create!(user_id: users[2].id, visited_at: date - 6.days)
UserVisit.create!(user_id: users[3].id, visited_at: date - 3.days)
UserVisit.create!(user_id: users[3].id, visited_at: date - 5.days)
UserVisit.create!(user_id: users[1].id, visited_at: date - 66.hours)
UserVisit.create!(user_id: users[2].id, visited_at: date - 8.days)
UserVisit.create!(user_id: users[3].id, visited_at: date - 13.days)
UserVisit.create!(user_id: users[0].id, visited_at: date - 24.days)
UserVisit.create!(user_id: users[4].id, visited_at: date - 19.days)
UserVisit.create!(user_id: users[2].id, visited_at: date - 31.days)
end
fab!(:users) { Fabricate.times(5, :user) }
let(:date) { DateTime.parse("2024-03-01 13:00") }
describe ".participating_users" do
it "returns no participating users by default" do
pu = described_class.participating_users
expect(pu[:last_day]).to eq(0)
expect(pu[:"7_days"]).to eq(0)
expect(pu[:"30_days"]).to eq(0)
end
it "returns users who have reacted to a post" do
Fabricate(:user_action, action_type: UserAction::LIKE)
expect(described_class.participating_users[:last_day]).to eq(1)
end
it "returns users who have created a new topic" do
Fabricate(:user_action, action_type: UserAction::NEW_TOPIC)
expect(described_class.participating_users[:last_day]).to eq(1)
end
it "returns users who have replied to a post" do
Fabricate(:user_action, action_type: UserAction::REPLY)
expect(described_class.participating_users[:last_day]).to eq(1)
end
it "returns users who have created a new PM" do
Fabricate(:user_action, action_type: UserAction::NEW_PRIVATE_MESSAGE)
expect(described_class.participating_users[:last_day]).to eq(1)
end
end
describe ".visitors" do
before do
ApplicationRequest.enable
create_page_views_and_user_visit_records(date, users)
end
after { ApplicationRequest.disable }
it "estimates the number of visitors for each of the previous 1 day, 7 days and 30 days periods" do
freeze_time(date) do
visitors = described_class.visitors
# anon page views: 2
# logged-in page views: 1
# logged-in visitors: 1
# we can estimate the number of unique anon visitors by dividing the
# number of anon page views by the average number of logged-in page
# views per logged-in visitor.
# in this case, the estimated number of anon visitors is 2 / (1 / 1) = 2.
# total visitors = logged-in visitors (1) + estimated anon visitors (2) = 3
expect(visitors[:last_day]).to eq(3)
# anon page views: 6
# logged-in page views: 10
# logged-in visitors: 4
# we can estimate the number of unique anon visitors by dividing the
# number of anon page views by the average number of logged-in page
# views per logged-in visitor.
# in this case, the estimated number of anon visitors is 6 / (10 / 4) ~= 2.
# total visitors = logged-in visitors (4) + estimated anon visitors (2) = 6
expect(visitors[:"7_days"]).to eq(6)
# anon page views: 11
# logged-in page views: 14
# logged-in visitors: 5
# we can estimate the number of unique anon visitors by dividing the
# number of anon page views by the average number of logged-in page
# views per logged-in visitor.
# in this case, the estimated number of anon visitors is 11 / (14 / 5) ~= 4.
# total visitors = logged-in visitors (5) + estimated anon visitors (4) = 9
expect(visitors[:"30_days"]).to eq(9)
end
end
it "is the same as the number of anon page views when there are no logged in visitors" do
freeze_time(date) do
UserVisit.delete_all
visitors = described_class.visitors
expect(visitors[:last_day]).to eq(2)
expect(visitors[:"7_days"]).to eq(6)
expect(visitors[:"30_days"]).to eq(11)
end
end
end
describe ".eu_visitors" do
before do
ApplicationRequest.enable
create_page_views_and_user_visit_records(date, users)
users[0].update!(ip_address: IPAddr.new("60.23.1.42"))
users[1].update!(ip_address: IPAddr.new("90.19.255.63"))
users[2].update!(ip_address: IPAddr.new("8.33.134.244"))
users[3].update!(ip_address: IPAddr.new("2.74.0.98"))
users[4].update!(ip_address: IPAddr.new("88.82.3.101"))
# EU IP addresses
DiscourseIpInfo.stubs(:get).with("60.23.1.42").returns({ country_code: "FR" }) # users[0]
DiscourseIpInfo.stubs(:get).with("2.74.0.98").returns({ country_code: "NL" }) # users[3]
DiscourseIpInfo.stubs(:get).with("88.82.3.101").returns({ country_code: "DE" }) # users[4]
# non-EU IP addresses
DiscourseIpInfo.stubs(:get).with("90.19.255.63").returns({ country_code: "US" }) # users[1]
DiscourseIpInfo.stubs(:get).with("8.33.134.244").returns({ country_code: "SA" }) # users[2]
end
after { ApplicationRequest.disable }
it "estimates the number of EU visitors for each of the previous 1 day, 7 days and 30 days periods" do
freeze_time(date) do
eu_visitors = described_class.eu_visitors
# anon page views: 2
# logged-in page views: 1
# logged-in visitors: 1
# EU logged-in visitors: 1 (users[0])
# we can estimate the number of unique EU anon visitors by dividing the
# number of anon page views by the average number of logged-in page
# views per logged-in visitor, then multiplying the result by the ratio
# of EU logged-in visitors to all logged-in visitors.
# in this case, the estimated number of EU anon visitors is 2 / (1 / 1) * (1 / 1) = 2
# total EU visitors = EU logged-in visitors (1) + estimated EU anon visitors (2) = 3
expect(eu_visitors[:last_day]).to eq(3)
# anon page views: 6
# logged-in page views: 10
# logged-in visitors: 4
# EU logged-in visitors: 2 (users[0], users[3])
# we can estimate the number of unique EU anon visitors by dividing the
# number of anon page views by the average number of logged-in page
# views per logged-in visitor, then multiplying the result by the ratio
# of EU logged-in visitors to all logged-in visitors.
# in this case, the estimated number of EU anon visitors is 6 / (10 / 4) * (2 / 4) ~= 1
# total EU visitors = EU logged-in visitors (2) + estimated EU anon visitors (1) = 3
expect(eu_visitors[:"7_days"]).to eq(3)
# anon page views: 11
# logged-in page views: 14
# logged-in visitors: 5
# EU logged-in visitors: 3 (users[0], users[3], users[4])
# we can estimate the number of unique EU anon visitors by dividing the
# number of anon page views by the average number of logged-in page
# views per logged-in visitor, then multiplying the result by the ratio
# of EU logged-in visitors to all logged-in visitors.
# in this case, the estimated number of EU anon visitors is 11 / (14 / 5) * (3 / 5) ~= 1
# total EU visitors = EU logged-in visitors (3) + estimated EU anon visitors (2) = 5
expect(eu_visitors[:"30_days"]).to eq(5)
end
end
it "returns 0 for EU visitors when there are no logged-in users" do
freeze_time(date) do
UserVisit.delete_all
eu_visitors = described_class.eu_visitors
expect(eu_visitors[:last_day]).to eq(0)
expect(eu_visitors[:"7_days"]).to eq(0)
expect(eu_visitors[:"30_days"]).to eq(0)
end
end
end
end