# 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 ".users" do before { User.real.destroy_all } it "doesn't count silenced or inactive users" do res = described_class.users expect(res[:last_day]).to eq(0) expect(res[:"7_days"]).to eq(0) expect(res[:"30_days"]).to eq(0) expect(res[:count]).to eq(0) user = Fabricate(:user, active: true) user2 = Fabricate(:user, active: true) res = described_class.users expect(res[:last_day]).to eq(2) expect(res[:"7_days"]).to eq(2) expect(res[:"30_days"]).to eq(2) expect(res[:count]).to eq(2) user.update!(active: false) res = described_class.users expect(res[:last_day]).to eq(1) expect(res[:"7_days"]).to eq(1) expect(res[:"30_days"]).to eq(1) expect(res[:count]).to eq(1) user2.update!(silenced_till: 1.month.from_now) res = described_class.users expect(res[:last_day]).to eq(0) expect(res[:"7_days"]).to eq(0) expect(res[:"30_days"]).to eq(0) expect(res[:count]).to eq(0) end it "doesn't include unapproved users if must_approve_users setting is true" do SiteSetting.must_approve_users = false user = Fabricate(:user, active: true, approved: false) res = described_class.users expect(res[:last_day]).to eq(1) expect(res[:"7_days"]).to eq(1) expect(res[:"30_days"]).to eq(1) expect(res[:count]).to eq(1) SiteSetting.must_approve_users = true # changing the site setting approves all existing users # flip this one back to unapproved user.reload.update!(approved: false) res = described_class.users expect(res[:last_day]).to eq(0) expect(res[:"7_days"]).to eq(0) expect(res[:"30_days"]).to eq(0) expect(res[:count]).to eq(0) user.update!(approved: true) res = described_class.users expect(res[:last_day]).to eq(1) expect(res[:"7_days"]).to eq(1) expect(res[:"30_days"]).to eq(1) expect(res[:count]).to eq(1) end it "counts users in the time windows they were created in" do res = described_class.users expect(res[:last_day]).to eq(0) expect(res[:"7_days"]).to eq(0) expect(res[:"30_days"]).to eq(0) expect(res[:count]).to eq(0) Fabricate(:user, active: true, created_at: 31.days.ago) res = described_class.users expect(res[:last_day]).to eq(0) expect(res[:"7_days"]).to eq(0) expect(res[:"30_days"]).to eq(0) expect(res[:count]).to eq(1) Fabricate(:user, active: true, created_at: 28.days.ago) res = described_class.users expect(res[:last_day]).to eq(0) expect(res[:"7_days"]).to eq(0) expect(res[:"30_days"]).to eq(1) expect(res[:count]).to eq(2) Fabricate(:user, active: true, created_at: 6.days.ago) res = described_class.users expect(res[:last_day]).to eq(0) expect(res[:"7_days"]).to eq(1) expect(res[:"30_days"]).to eq(2) expect(res[:count]).to eq(3) Fabricate(:user, active: true, created_at: 6.hours.ago) res = described_class.users expect(res[:last_day]).to eq(1) expect(res[:"7_days"]).to eq(2) expect(res[:"30_days"]).to eq(3) expect(res[:count]).to eq(4) end end 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