mirror of
https://github.com/discourse/discourse.git
synced 2024-12-01 13:09:33 -06:00
14b436923c
### UI changes
All of the UI changes described are gated behind the `use_legacy_pageviews`
site setting.
This commit changes the admin dashboard pageviews report to
use the "Consolidated Pageviews with Browser Detection" report
introduced in 2f2da72747
with
the following changes:
* The report name is changed to "Site traffic"
* The pageview count on the dashboard is counting only using the new method
* The old "Consolidated Pageviews" report is renamed as "Consolidated Legacy Pageviews"
* By default "known crawlers" and "other" sources of pageviews are hidden on the report
When `use_legacy_pageviews` is `true`, we do not show or allow running
the "Site traffic" report for admins. When `use_legacy_pageviews` is `false`,
we do not show or allow running the following legacy reports:
* consolidated_page_views
* consolidated_page_views_browser_detection
* page_view_anon_reqs
* page_view_logged_in_reqs
### Historical data changes
Also part of this change is that, since we introduced our new "Consolidated
Pageviews with Browser Detection" report, some admins are confused at either:
* The lack of data before a certain date , which didn’t exist before
we started collecting it
* Comparing this and the current "Consolidated Pageviews" report data,
which rolls up "Other Pageviews" into "Anonymous Browser" and so it
appears inaccurate
All pageview data in the new report before the date where the _first_
anon or logged in browser pageview was recorded is now hidden.
1893 lines
58 KiB
Ruby
1893 lines
58 KiB
Ruby
# frozen_string_literal: true
|
||
|
||
RSpec.describe Report do
|
||
let(:user) { Fabricate(:user) }
|
||
let(:category_1) { Fabricate(:category, user: user) }
|
||
let(:category_2) { Fabricate(:category, parent_category: category_1, user: user) } # id: 2
|
||
let(:category_3) { Fabricate(:category, user: user) }
|
||
|
||
shared_examples "no data" do
|
||
context "with no data" do
|
||
it "returns an empty report" do
|
||
expect(report.data).to be_blank
|
||
end
|
||
end
|
||
end
|
||
|
||
shared_examples "category filtering" do
|
||
it "returns the filtered data" do
|
||
expect(report.total).to eq 1
|
||
end
|
||
end
|
||
|
||
shared_examples "category filtering on subcategories" do
|
||
it "returns the filtered data" do
|
||
expect(report.total).to eq(1)
|
||
end
|
||
end
|
||
|
||
shared_examples "with data x/y" do
|
||
it "returns today's data" do
|
||
expect(report.data.select { |v| v[:x].today? }).to be_present
|
||
end
|
||
|
||
it "returns correct data for period" do
|
||
expect(report.data[0][:y]).to eq 3
|
||
end
|
||
|
||
it "returns total" do
|
||
expect(report.total).to eq 4
|
||
end
|
||
|
||
it "returns previous 30 day’s data" do
|
||
expect(report.prev30Days).to be_present
|
||
end
|
||
end
|
||
|
||
describe "counting" do
|
||
describe "requests" do
|
||
subject(:json) { Report.find("http_total_reqs").as_json }
|
||
|
||
before do
|
||
freeze_time_safe
|
||
|
||
# today, an incomplete day:
|
||
application_requests = [
|
||
{
|
||
date: 0.days.ago.to_time,
|
||
req_type: ApplicationRequest.req_types["http_total"],
|
||
count: 1,
|
||
},
|
||
]
|
||
|
||
# 60 complete days:
|
||
30.times.each do |i|
|
||
application_requests.concat(
|
||
[
|
||
{
|
||
date: (i + 1).days.ago.to_time,
|
||
req_type: ApplicationRequest.req_types["http_total"],
|
||
count: 10,
|
||
},
|
||
],
|
||
)
|
||
end
|
||
30.times.each do |i|
|
||
application_requests.concat(
|
||
[
|
||
{
|
||
date: (31 + i).days.ago.to_time,
|
||
req_type: ApplicationRequest.req_types["http_total"],
|
||
count: 100,
|
||
},
|
||
],
|
||
)
|
||
end
|
||
|
||
ApplicationRequest.insert_all(application_requests)
|
||
end
|
||
|
||
it "counts the correct records" do
|
||
expect(json[:data].size).to eq(31) # today and 30 full days
|
||
expect(json[:data][0..-2].sum { |d| d[:y] }).to eq(300)
|
||
expect(json[:prev30Days]).to eq(3000)
|
||
end
|
||
end
|
||
|
||
describe "topics" do
|
||
before do
|
||
Report.clear_cache
|
||
freeze_time_safe
|
||
user = Fabricate(:user)
|
||
topics =
|
||
((0..32).to_a + [60, 61, 62, 63]).map do |i|
|
||
date = i.days.ago
|
||
{
|
||
user_id: user.id,
|
||
last_post_user_id: user.id,
|
||
title: "topic #{i}",
|
||
category_id: SiteSetting.uncategorized_category_id,
|
||
bumped_at: date,
|
||
created_at: date,
|
||
updated_at: date,
|
||
}
|
||
end
|
||
Topic.insert_all(topics)
|
||
end
|
||
|
||
it "counts the correct records" do
|
||
json = Report.find("topics").as_json
|
||
expect(json[:data].size).to eq(31)
|
||
expect(json[:prev30Days]).to eq(3)
|
||
|
||
# lets make sure we can ask for the correct options for the report
|
||
json =
|
||
Report.find(
|
||
"topics",
|
||
start_date: 5.days.ago.beginning_of_day,
|
||
end_date: 1.day.ago.end_of_day,
|
||
facets: [:prev_period],
|
||
).as_json
|
||
|
||
expect(json[:prev_period]).to eq(5)
|
||
expect(json[:data].length).to eq(5)
|
||
expect(json[:prev30Days]).to eq(nil)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "visits report" do
|
||
let(:report) { Report.find("visits") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with visits" do
|
||
let(:user) { Fabricate(:user) }
|
||
|
||
it "returns a report with data" do
|
||
freeze_time_safe
|
||
user.user_visits.create(visited_at: 1.hour.from_now)
|
||
user.user_visits.create(visited_at: 1.day.ago)
|
||
user.user_visits.create(visited_at: 2.days.ago, mobile: true)
|
||
user.user_visits.create(visited_at: 45.days.ago)
|
||
user.user_visits.create(visited_at: 46.days.ago, mobile: true)
|
||
|
||
expect(report.data).to be_present
|
||
expect(report.data.count).to eq(3)
|
||
expect(report.data.select { |v| v[:x].today? }).to be_present
|
||
expect(report.prev30Days).to eq(2)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "mobile visits report" do
|
||
let(:report) { Report.find("mobile_visits") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with visits" do
|
||
let(:user) { Fabricate(:user) }
|
||
|
||
it "returns a report with data" do
|
||
freeze_time_safe
|
||
user.user_visits.create(visited_at: 1.hour.from_now)
|
||
user.user_visits.create(visited_at: 2.days.ago, mobile: true)
|
||
user.user_visits.create(visited_at: 45.days.ago)
|
||
user.user_visits.create(visited_at: 46.days.ago, mobile: true)
|
||
|
||
expect(report.data).to be_present
|
||
expect(report.data.count).to eq(1)
|
||
expect(report.data.select { |v| v[:x].today? }).not_to be_present
|
||
expect(report.prev30Days).to eq(1)
|
||
end
|
||
end
|
||
end
|
||
|
||
%i[signup topic post flag like email].each do |arg|
|
||
describe "#{arg} report" do
|
||
pluralized = arg.to_s.pluralize
|
||
|
||
let(:report) { Report.find(pluralized) }
|
||
|
||
context "with no #{pluralized}" do
|
||
it "returns an empty report" do
|
||
expect(report.data).to be_blank
|
||
end
|
||
end
|
||
|
||
context "with #{pluralized}" do
|
||
before(:each) do
|
||
freeze_time_safe
|
||
|
||
if arg == :flag
|
||
user = Fabricate(:user, refresh_auto_groups: true)
|
||
topic = Fabricate(:topic, user: user)
|
||
builder = ->(dt) do
|
||
PostActionCreator.create(
|
||
user,
|
||
Fabricate(:post, topic: topic, user: user),
|
||
:spam,
|
||
created_at: dt,
|
||
)
|
||
end
|
||
elsif arg == :signup
|
||
builder = ->(dt) { Fabricate(:user, created_at: dt) }
|
||
else
|
||
user = Fabricate(:user)
|
||
factories = { email: :email_log }
|
||
builder = ->(dt) { Fabricate(factories[arg] || arg, created_at: dt, user: user) }
|
||
end
|
||
|
||
[
|
||
DateTime.now,
|
||
1.hour.ago,
|
||
1.hour.ago,
|
||
1.day.ago,
|
||
2.days.ago,
|
||
30.days.ago,
|
||
35.days.ago,
|
||
].each(&builder)
|
||
end
|
||
|
||
it "returns today's, total and previous 30 day's data" do
|
||
expect(report.data.select { |v| v[:x].today? }).to be_present
|
||
expect(report.total).to eq 7
|
||
expect(report.prev30Days).to be_present
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
%i[
|
||
http_total
|
||
http_2xx
|
||
http_background
|
||
http_3xx
|
||
http_4xx
|
||
http_5xx
|
||
page_view_crawler
|
||
page_view_logged_in
|
||
page_view_anon
|
||
].each do |request_type|
|
||
describe "#{request_type} request reports" do
|
||
let(:report) do
|
||
Report.find("#{request_type}_reqs", start_date: 10.days.ago.to_time, end_date: Time.now)
|
||
end
|
||
|
||
context "with no #{request_type} records" do
|
||
it "returns an empty report" do
|
||
expect(report.data).to be_blank
|
||
end
|
||
end
|
||
|
||
context "with #{request_type}" do
|
||
before do
|
||
freeze_time_safe
|
||
application_requests = [
|
||
{
|
||
date: 35.days.ago.to_time,
|
||
req_type: ApplicationRequest.req_types[request_type.to_s],
|
||
count: 35,
|
||
},
|
||
{
|
||
date: 7.days.ago.to_time,
|
||
req_type: ApplicationRequest.req_types[request_type.to_s],
|
||
count: 8,
|
||
},
|
||
{ date: Time.now, req_type: ApplicationRequest.req_types[request_type.to_s], count: 1 },
|
||
{
|
||
date: 1.day.ago.to_time,
|
||
req_type: ApplicationRequest.req_types[request_type.to_s],
|
||
count: 2,
|
||
},
|
||
{
|
||
date: 2.days.ago.to_time,
|
||
req_type: ApplicationRequest.req_types[request_type.to_s],
|
||
count: 3,
|
||
},
|
||
]
|
||
ApplicationRequest.insert_all(application_requests)
|
||
end
|
||
|
||
it "returns a report with data" do
|
||
# expected number of records
|
||
expect(report.data.count).to eq 4
|
||
|
||
# sorts the data from oldest to latest dates
|
||
expect(report.data[0][:y]).to eq(8) # 7 days ago
|
||
expect(report.data[1][:y]).to eq(3) # 2 days ago
|
||
expect(report.data[2][:y]).to eq(2) # 1 day ago
|
||
expect(report.data[3][:y]).to eq(1) # today
|
||
|
||
# today's data
|
||
expect(report.data.find { |value| value[:x] == Date.today }).to be_present
|
||
|
||
# total data
|
||
expect(report.total).to eq 49
|
||
|
||
#previous 30 days of data
|
||
expect(report.prev30Days).to eq 35
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "page_view_total_reqs" do
|
||
before do
|
||
freeze_time(Time.now.at_midnight)
|
||
Theme.clear_default!
|
||
end
|
||
|
||
let(:report) { Report.find("page_view_total_reqs") }
|
||
|
||
context "with no data" do
|
||
it "works" do
|
||
expect(report.data).to be_empty
|
||
end
|
||
end
|
||
|
||
context "with data" do
|
||
before do
|
||
CachedCounting.reset
|
||
CachedCounting.enable
|
||
ApplicationRequest.enable
|
||
end
|
||
|
||
after do
|
||
CachedCounting.reset
|
||
ApplicationRequest.disable
|
||
CachedCounting.disable
|
||
end
|
||
|
||
context "when use_legacy_pageviews is true" do
|
||
before { SiteSetting.use_legacy_pageviews = true }
|
||
|
||
it "works and does not count browser or mobile pageviews" do
|
||
3.times { ApplicationRequest.increment!(:page_view_crawler) }
|
||
8.times { ApplicationRequest.increment!(:page_view_logged_in) }
|
||
6.times { ApplicationRequest.increment!(:page_view_logged_in_browser) }
|
||
2.times { ApplicationRequest.increment!(:page_view_logged_in_mobile) }
|
||
2.times { ApplicationRequest.increment!(:page_view_anon) }
|
||
1.times { ApplicationRequest.increment!(:page_view_anon_browser) }
|
||
4.times { ApplicationRequest.increment!(:page_view_anon_mobile) }
|
||
|
||
CachedCounting.flush
|
||
|
||
expect(report.data.sum { |r| r[:y] }).to eq(13)
|
||
end
|
||
end
|
||
|
||
context "when use_legacy_pageviews is false" do
|
||
before { SiteSetting.use_legacy_pageviews = false }
|
||
|
||
it "works and does not count mobile pageviews, and only counts browser pageviews" do
|
||
3.times { ApplicationRequest.increment!(:page_view_crawler) }
|
||
8.times { ApplicationRequest.increment!(:page_view_logged_in) }
|
||
6.times { ApplicationRequest.increment!(:page_view_logged_in_browser) }
|
||
2.times { ApplicationRequest.increment!(:page_view_logged_in_mobile) }
|
||
2.times { ApplicationRequest.increment!(:page_view_anon) }
|
||
1.times { ApplicationRequest.increment!(:page_view_anon_browser) }
|
||
4.times { ApplicationRequest.increment!(:page_view_anon_mobile) }
|
||
|
||
CachedCounting.flush
|
||
|
||
expect(report.data.sum { |r| r[:y] }).to eq(7)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "user to user private messages with replies" do
|
||
let(:report) { Report.find("user_to_user_private_messages_with_replies") }
|
||
let(:user) { Fabricate(:user) }
|
||
let(:topic) { Fabricate(:topic, created_at: 1.hour.ago, user: user) }
|
||
|
||
it "topic report).to not include private messages" do
|
||
Fabricate(:private_message_topic, created_at: 1.hour.ago, user: user)
|
||
topic
|
||
report = Report.find("topics")
|
||
expect(report.data[0][:y]).to eq(1)
|
||
expect(report.total).to eq(1)
|
||
end
|
||
|
||
it "post report).to not include private messages" do
|
||
Fabricate(:private_message_post, created_at: 1.hour.ago)
|
||
Fabricate(:post)
|
||
report = Report.find("posts")
|
||
expect(report.data[0][:y]).to eq 1
|
||
expect(report.total).to eq 1
|
||
end
|
||
|
||
context "with no private messages" do
|
||
it "returns an empty report" do
|
||
expect(report.data).to be_blank
|
||
end
|
||
|
||
context "with some public posts" do
|
||
it "returns an empty report" do
|
||
Fabricate(:post, topic: topic, user: user)
|
||
Fabricate(:post, topic: topic, user: user)
|
||
expect(report.data).to be_blank
|
||
expect(report.total).to eq 0
|
||
end
|
||
end
|
||
end
|
||
|
||
context "with some private messages" do
|
||
before do
|
||
Fabricate(:private_message_post, created_at: 25.hours.ago, user: user)
|
||
Fabricate(:private_message_post, created_at: 1.hour.ago, user: user)
|
||
Fabricate(:private_message_post, created_at: 1.hour.ago, user: user)
|
||
end
|
||
|
||
it "returns correct data" do
|
||
expect(report.data[0][:y]).to eq 1
|
||
expect(report.data[1][:y]).to eq 2
|
||
expect(report.total).to eq 3
|
||
end
|
||
|
||
context "with some public posts" do
|
||
before do
|
||
Fabricate(:post, user: user, topic: topic)
|
||
Fabricate(:post, user: user, topic: topic)
|
||
end
|
||
|
||
it "returns correct data" do
|
||
expect(report.data[0][:y]).to eq 1
|
||
expect(report.data[1][:y]).to eq 2
|
||
expect(report.total).to eq 3
|
||
end
|
||
end
|
||
end
|
||
|
||
context "with private message from system user" do
|
||
before do
|
||
Fabricate(:private_message_post, created_at: 1.hour.ago, user: Discourse.system_user)
|
||
end
|
||
|
||
it "does not include system users" do
|
||
expect(report.data).to be_blank
|
||
expect(report.total).to eq 0
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "user to user private messages" do
|
||
let(:report) { Report.find("user_to_user_private_messages") }
|
||
|
||
context "with private message from system user" do
|
||
before do
|
||
Fabricate(:private_message_post, created_at: 1.hour.ago, user: Discourse.system_user)
|
||
end
|
||
|
||
it "does not include system users" do
|
||
expect(report.data).to be_blank
|
||
expect(report.total).to eq 0
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "users by trust level report" do
|
||
let(:report) { Report.find("users_by_trust_level") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with users at different trust levels" do
|
||
before do
|
||
3.times { Fabricate(:user, trust_level: TrustLevel[0]) }
|
||
2.times { Fabricate(:user, trust_level: TrustLevel[2]) }
|
||
Fabricate(:user, trust_level: TrustLevel[4])
|
||
end
|
||
|
||
it "returns a report with data" do
|
||
expect(report.data).to be_present
|
||
expect(report.data.find { |d| d[:x] == TrustLevel[0] }[:y]).to eq 3
|
||
expect(report.data.find { |d| d[:x] == TrustLevel[2] }[:y]).to eq 2
|
||
expect(report.data.find { |d| d[:x] == TrustLevel[4] }[:y]).to eq 1
|
||
|
||
expect(
|
||
report.data.find { |d| d[:x] == TrustLevel[0] }[:url],
|
||
).to eq "/admin/users/list/newuser"
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "new contributors report" do
|
||
let(:report) { Report.find("new_contributors") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with contributors" do
|
||
before do
|
||
jeff = Fabricate(:user)
|
||
jeff.user_stat = UserStat.new(new_since: 1.hour.ago, first_post_created_at: 1.day.ago)
|
||
|
||
regis = Fabricate(:user)
|
||
regis.user_stat = UserStat.new(new_since: 1.hour.ago, first_post_created_at: 2.days.ago)
|
||
|
||
hawk = Fabricate(:user)
|
||
hawk.user_stat = UserStat.new(new_since: 1.hour.ago, first_post_created_at: 2.days.ago)
|
||
end
|
||
|
||
it "returns a report with data" do
|
||
expect(report.data).to be_present
|
||
|
||
expect(report.data[0][:y]).to eq 2
|
||
expect(report.data[1][:y]).to eq 1
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "users by types level report" do
|
||
let(:report) { Report.find("users_by_type") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with users at different trust levels" do
|
||
before do
|
||
3.times { Fabricate(:user, admin: true) }
|
||
2.times { Fabricate(:user, moderator: true) }
|
||
UserSilencer.silence(Fabricate(:user, refresh_auto_groups: true), Fabricate.build(:admin))
|
||
Fabricate(:user, suspended_till: 1.week.from_now, suspended_at: 1.day.ago)
|
||
end
|
||
|
||
it "returns a report with data" do
|
||
expect(report.data).to be_present
|
||
|
||
label = Proc.new { |key| I18n.t("reports.users_by_type.xaxis_labels.#{key}") }
|
||
expect(report.data.find { |d| d[:x] == label.call("admin") }[:y]).to eq 3
|
||
expect(report.data.find { |d| d[:x] == label.call("moderator") }[:y]).to eq 2
|
||
expect(report.data.find { |d| d[:x] == label.call("silenced") }[:y]).to eq 1
|
||
expect(report.data.find { |d| d[:x] == label.call("suspended") }[:y]).to eq 1
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "trending search report" do
|
||
let(:report) { Report.find("trending_search") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with different searches" do
|
||
before do
|
||
SearchLog.log(term: "ruby", search_type: :header, ip_address: "127.0.0.1")
|
||
|
||
SearchLog.create!(
|
||
term: "ruby",
|
||
search_result_id: 1,
|
||
search_type: 1,
|
||
ip_address: "127.0.0.1",
|
||
user_id: Fabricate(:user).id,
|
||
)
|
||
|
||
SearchLog.log(term: "ruby", search_type: :header, ip_address: "127.0.0.2")
|
||
SearchLog.log(term: "php", search_type: :header, ip_address: "127.0.0.1")
|
||
end
|
||
|
||
after { SearchLog.clear_debounce_cache! }
|
||
|
||
it "returns a report with data" do
|
||
expect(report.data[0][:term]).to eq("ruby")
|
||
expect(report.data[0][:searches]).to eq(3)
|
||
expect(report.data[0][:ctr]).to eq(33.4)
|
||
|
||
expect(report.data[1][:term]).to eq("php")
|
||
expect(report.data[1][:searches]).to eq(1)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "DAU/MAU report" do
|
||
let(:report) { Report.find("dau_by_mau") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with different users/visits" do
|
||
before do
|
||
freeze_time_safe
|
||
|
||
arpit = Fabricate(:user)
|
||
arpit.user_visits.create(visited_at: 1.day.ago)
|
||
|
||
sam = Fabricate(:user)
|
||
sam.user_visits.create(visited_at: 2.days.ago)
|
||
|
||
robin = Fabricate(:user)
|
||
robin.user_visits.create(visited_at: 2.days.ago)
|
||
|
||
michael = Fabricate(:user)
|
||
michael.user_visits.create(visited_at: 35.days.ago)
|
||
|
||
gerhard = Fabricate(:user)
|
||
gerhard.user_visits.create(visited_at: 45.days.ago)
|
||
end
|
||
|
||
it "returns a report with data" do
|
||
expect(report.data.first[:y]).to eq(100)
|
||
expect(report.data.last[:y]).to eq(33.34)
|
||
expect(report.prev30Days).to eq(75)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "Daily engaged users" do
|
||
let(:report) { Report.find("daily_engaged_users") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with different activities" do
|
||
before do
|
||
freeze_time_safe
|
||
|
||
UserActionManager.enable
|
||
|
||
arpit = Fabricate(:user)
|
||
sam = Fabricate(:user)
|
||
|
||
jeff = Fabricate(:user, created_at: 1.day.ago, refresh_auto_groups: true)
|
||
post = create_post(user: jeff, created_at: 1.day.ago)
|
||
PostActionCreator.like(arpit, post)
|
||
PostActionCreator.like(sam, post)
|
||
end
|
||
|
||
it "returns a report with data" do
|
||
expect(report.data.first[:y]).to eq(1)
|
||
expect(report.data.last[:y]).to eq(2)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "posts counts" do
|
||
it "only counts regular posts" do
|
||
post = Fabricate(:post)
|
||
Fabricate(:moderator_post, topic: post.topic)
|
||
Fabricate.build(:post, post_type: Post.types[:whisper], topic: post.topic)
|
||
post.topic.add_small_action(Fabricate(:admin), "invited_group", "coolkids")
|
||
r = Report.find("posts")
|
||
expect(r.total).to eq(1)
|
||
expect(r.data[0][:y]).to eq(1)
|
||
end
|
||
end
|
||
|
||
describe "flags_status" do
|
||
let(:report) { Report.find("flags_status") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with flags" do
|
||
let(:flagger) { Fabricate(:user, refresh_auto_groups: true) }
|
||
let(:post) { Fabricate(:post, user: flagger) }
|
||
|
||
before { freeze_time }
|
||
|
||
it "returns a report with data" do
|
||
result =
|
||
PostActionCreator.new(flagger, post, PostActionType.types[:spam], message: "bad").perform
|
||
|
||
expect(result.success).to eq(true)
|
||
expect(report.data).to be_present
|
||
|
||
row = report.data[0]
|
||
expect(row[:post_type]).to eq("spam")
|
||
expect(row[:staff_username]).to eq(nil)
|
||
expect(row[:staff_id]).to eq(nil)
|
||
expect(row[:poster_username]).to eq(post.user.username)
|
||
expect(row[:poster_id]).to eq(post.user.id)
|
||
expect(row[:poster_avatar_template]).to be_present
|
||
expect(row[:flagger_id]).to eq(flagger.id)
|
||
expect(row[:flagger_username]).to eq(flagger.username)
|
||
expect(row[:flagger_avatar_template]).to be_present
|
||
expect(row[:resolution]).to eq("No action")
|
||
expect(row[:response_time]).to eq(nil)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "post_edits" do
|
||
let(:report) { Report.find("post_edits") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with edits" do
|
||
let(:editor) { Fabricate(:user) }
|
||
let(:post) { Fabricate(:post) }
|
||
|
||
before do
|
||
freeze_time
|
||
|
||
post.revise(
|
||
post.user,
|
||
{ raw: "updated body by author", edit_reason: "not cool" },
|
||
force_new_version: true,
|
||
)
|
||
post.revise(editor, raw: "updated body", edit_reason: "not cool")
|
||
end
|
||
|
||
it "returns a report with data" do
|
||
expect(report.data).to be_present
|
||
expect(report.data.count).to be(1)
|
||
|
||
row = report.data[0]
|
||
expect(row[:editor_id]).to eq(editor.id)
|
||
expect(row[:editor_username]).to eq(editor.username)
|
||
expect(row[:editor_avatar_template]).to be_present
|
||
expect(row[:author_id]).to eq(post.user.id)
|
||
expect(row[:author_username]).to eq(post.user.username)
|
||
expect(row[:author_avatar_template]).to be_present
|
||
expect(row[:edit_reason]).to eq("not cool")
|
||
expect(row[:post_raw]).to eq("updated body")
|
||
expect(row[:post_number]).to eq(post.post_number)
|
||
expect(row[:topic_id]).to eq(post.topic.id)
|
||
end
|
||
end
|
||
|
||
context "with editor filter" do
|
||
fab!(:posts) { Fabricate.times(3, :post) }
|
||
|
||
fab!(:editor_with_two_edits) do
|
||
Fabricate(:user).tap do |user|
|
||
2.times { |i| posts[i].revise(user, { raw: "edit #{i + 1}" }) }
|
||
end
|
||
end
|
||
|
||
fab!(:editor_with_one_edit) do
|
||
Fabricate(:user).tap { |user| posts.last.revise(user, { raw: "edit 3" }) }
|
||
end
|
||
|
||
let(:report_with_one_edit) do
|
||
Report.find("post_edits", { filters: { "editor" => editor_with_one_edit.username } })
|
||
end
|
||
|
||
let(:report_with_two_edits) do
|
||
Report.find("post_edits", { filters: { "editor" => editor_with_two_edits.username } })
|
||
end
|
||
|
||
it "returns a report for a given editor" do
|
||
expect(report_with_one_edit.data.count).to be(1)
|
||
expect(report_with_two_edits.data.count).to be(2)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "moderator activity" do
|
||
let(:report) { Report.find("moderators_activity") }
|
||
|
||
let(:sam) { Fabricate(:user, moderator: true, username: "sam") }
|
||
|
||
let(:jeff) { Fabricate(:user, moderator: true, username: "jeff", refresh_auto_groups: true) }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with moderators" do
|
||
before { freeze_time(Date.today) }
|
||
|
||
context "with moderators order" do
|
||
before do
|
||
Fabricate(:post, user: sam)
|
||
Fabricate(:post, user: jeff)
|
||
end
|
||
|
||
it "returns the moderators in alphabetical order" do
|
||
expect(report.data[0][:username]).to eq("jeff")
|
||
expect(report.data[1][:username]).to eq("sam")
|
||
end
|
||
end
|
||
|
||
context "with time read" do
|
||
before do
|
||
sam.user_visits.create(visited_at: 2.days.ago, time_read: 200)
|
||
sam.user_visits.create(visited_at: 1.day.ago, time_read: 100)
|
||
|
||
jeff.user_visits.create(visited_at: 2.days.ago, time_read: 1000)
|
||
jeff.user_visits.create(visited_at: 1.day.ago, time_read: 2000)
|
||
|
||
Fabricate(:topic, created_at: 1.day.ago)
|
||
end
|
||
|
||
it "returns the correct read times" do
|
||
expect(report.data[0][:username]).to eq("jeff")
|
||
expect(report.data[0][:time_read]).to eq(3000)
|
||
expect(report.data[1][:username]).to eq("sam")
|
||
expect(report.data[1][:time_read]).to eq(300)
|
||
end
|
||
end
|
||
|
||
context "with flags" do
|
||
before do
|
||
flagged_post = Fabricate(:post)
|
||
result = PostActionCreator.off_topic(jeff, flagged_post)
|
||
result.reviewable.perform(jeff, :agree_and_keep)
|
||
end
|
||
|
||
it "returns the correct flag counts" do
|
||
expect(report.data.count).to eq(1)
|
||
expect(report.data[0][:flag_count]).to eq(1)
|
||
expect(report.data[0][:username]).to eq("jeff")
|
||
end
|
||
end
|
||
|
||
context "with topics" do
|
||
before do
|
||
Fabricate(:topic, user: sam)
|
||
Fabricate(:topic, user: sam)
|
||
Fabricate(:topic, user: jeff)
|
||
end
|
||
|
||
it "returns the correct topic count" do
|
||
expect(report.data[0][:topic_count]).to eq(1)
|
||
expect(report.data[0][:username]).to eq("jeff")
|
||
expect(report.data[1][:topic_count]).to eq(2)
|
||
expect(report.data[1][:username]).to eq("sam")
|
||
end
|
||
|
||
context "with private messages" do
|
||
before { Fabricate(:private_message_topic, user: jeff) }
|
||
|
||
it "doesn’t count private topic" do
|
||
expect(report.data[0][:topic_count]).to eq(1)
|
||
expect(report.data[1][:topic_count]).to eq(2)
|
||
end
|
||
end
|
||
end
|
||
|
||
context "with posts" do
|
||
before do
|
||
Fabricate(:post, user: sam)
|
||
Fabricate(:post, user: sam)
|
||
Fabricate(:post, user: jeff)
|
||
end
|
||
|
||
it "returns the correct topic count" do
|
||
expect(report.data[0][:topic_count]).to eq(1)
|
||
expect(report.data[0][:username]).to eq("jeff")
|
||
expect(report.data[1][:topic_count]).to eq(2)
|
||
expect(report.data[1][:username]).to eq("sam")
|
||
end
|
||
|
||
context "with private messages" do
|
||
before { Fabricate(:private_message_post, user: jeff) }
|
||
|
||
it "doesn’t count private post" do
|
||
expect(report.data[0][:post_count]).to eq(1)
|
||
expect(report.data[1][:post_count]).to eq(2)
|
||
end
|
||
end
|
||
end
|
||
|
||
context "with private messages" do
|
||
before do
|
||
Fabricate(:post, user: sam)
|
||
Fabricate(:post, user: jeff)
|
||
Fabricate(:private_message_post, user: jeff)
|
||
end
|
||
|
||
it "returns the correct topic count" do
|
||
expect(report.data[0][:pm_count]).to eq(1)
|
||
expect(report.data[0][:username]).to eq("jeff")
|
||
expect(report.data[1][:pm_count]).to be_blank
|
||
expect(report.data[1][:username]).to eq("sam")
|
||
end
|
||
end
|
||
|
||
context "with revisions" do
|
||
before do
|
||
post = Fabricate(:post)
|
||
post.revise(sam, raw: "updated body", edit_reason: "not cool")
|
||
end
|
||
|
||
it "returns the correct revisions count" do
|
||
expect(report.data[0][:revision_count]).to eq(1)
|
||
expect(report.data[0][:username]).to eq("sam")
|
||
end
|
||
|
||
context "when revising own post" do
|
||
before do
|
||
post = Fabricate(:post, user: sam)
|
||
post.revise(sam, raw: "updated body")
|
||
end
|
||
|
||
it "doesn't count a revision on your own post" do
|
||
expect(report.data[0][:revision_count]).to eq(1)
|
||
expect(report.data[0][:username]).to eq("sam")
|
||
end
|
||
end
|
||
end
|
||
|
||
context "with previous data" do
|
||
before { Fabricate(:topic, user: sam, created_at: 1.year.ago) }
|
||
|
||
it "doesn’t count old data" do
|
||
expect(report.data[0][:topic_count]).to be_blank
|
||
expect(report.data[0][:username]).to eq("sam")
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "flags" do
|
||
let(:report) { Report.find("flags") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with data" do
|
||
include_examples "with data x/y"
|
||
|
||
before(:each) do
|
||
user = Fabricate(:user, refresh_auto_groups: true)
|
||
topic = Fabricate(:topic, user: user)
|
||
post0 = Fabricate(:post, topic: topic, user: user)
|
||
post1 =
|
||
Fabricate(:post, topic: Fabricate(:topic, category: category_2, user: user), user: user)
|
||
post2 = Fabricate(:post, topic: topic, user: user)
|
||
post3 = Fabricate(:post, topic: topic, user: user)
|
||
PostActionCreator.off_topic(user, post0)
|
||
PostActionCreator.off_topic(user, post1)
|
||
PostActionCreator.off_topic(user, post2)
|
||
PostActionCreator.create(user, post3, :off_topic, created_at: 45.days.ago)
|
||
end
|
||
|
||
context "with category filtering" do
|
||
let(:report) { Report.find("flags", filters: { category: category_2.id }) }
|
||
|
||
include_examples "category filtering"
|
||
|
||
context "with subcategories" do
|
||
let(:report) do
|
||
Report.find("flags", filters: { category: category_1.id, include_subcategories: true })
|
||
end
|
||
|
||
include_examples "category filtering on subcategories"
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "topics" do
|
||
let(:report) { Report.find("topics") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with data" do
|
||
include_examples "with data x/y"
|
||
|
||
before(:each) do
|
||
user = Fabricate(:user)
|
||
Fabricate(:topic, user: user)
|
||
Fabricate(:topic, category: category_2, user: user)
|
||
Fabricate(:topic, user: user)
|
||
Fabricate(:topic, created_at: 45.days.ago, user: user)
|
||
end
|
||
|
||
context "with category filtering" do
|
||
let(:report) { Report.find("topics", filters: { category: category_2.id }) }
|
||
|
||
include_examples "category filtering"
|
||
|
||
context "with subcategories" do
|
||
let(:report) do
|
||
Report.find("topics", filters: { category: category_1.id, include_subcategories: true })
|
||
end
|
||
|
||
include_examples "category filtering on subcategories"
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "exception report" do
|
||
before(:each) do
|
||
class Report
|
||
def self.report_exception_test(report)
|
||
report.data = x
|
||
end
|
||
end
|
||
end
|
||
|
||
it "returns a report with an exception error" do
|
||
report = Report.find("exception_test", wrap_exceptions_in_test: true)
|
||
expect(report.error).to eq(:exception)
|
||
end
|
||
end
|
||
|
||
describe "timeout report" do
|
||
before(:each) do
|
||
freeze_time
|
||
|
||
class Report
|
||
def self.report_timeout_test(report)
|
||
report.error =
|
||
wrap_slow_query(1) { ActiveRecord::Base.connection.execute("SELECT pg_sleep(5)") }
|
||
end
|
||
end
|
||
end
|
||
|
||
it "returns a report with a timeout error" do
|
||
report = Report.find("timeout_test")
|
||
expect(report.error).to eq(:timeout)
|
||
end
|
||
end
|
||
|
||
describe "unexpected error on report initialization" do
|
||
before do
|
||
@orig_logger = Rails.logger
|
||
Rails.logger = @fake_logger = FakeLogger.new
|
||
end
|
||
|
||
after { Rails.logger = @orig_logger }
|
||
|
||
it "returns no report" do
|
||
class ReportInitError < StandardError
|
||
end
|
||
|
||
Report.stubs(:new).raises(ReportInitError.new("x"))
|
||
|
||
report = Report.find("signups", wrap_exceptions_in_test: true)
|
||
|
||
expect(report).to be_nil
|
||
|
||
expect(@fake_logger.errors).to eq(["Couldn’t create report `signups`: <ReportInitError x>"])
|
||
end
|
||
end
|
||
|
||
describe "posts" do
|
||
let(:report) { Report.find("posts") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with data" do
|
||
include_examples "with data x/y"
|
||
|
||
before(:each) do
|
||
user = Fabricate(:user)
|
||
topic = Fabricate(:topic, user: user)
|
||
topic_with_category_id = Fabricate(:topic, category: category_2, user: user)
|
||
Fabricate(:post, topic: topic, user: user)
|
||
Fabricate(:post, topic: topic_with_category_id, user: user)
|
||
Fabricate(:post, topic: topic, user: user)
|
||
Fabricate(:post, created_at: 45.days.ago, topic: topic, user: user)
|
||
end
|
||
|
||
context "with category filtering" do
|
||
let(:report) { Report.find("posts", filters: { category: category_2.id }) }
|
||
|
||
include_examples "category filtering"
|
||
|
||
context "with subcategories" do
|
||
let(:report) do
|
||
Report.find("posts", filters: { category: category_1.id, include_subcategories: true })
|
||
end
|
||
|
||
include_examples "category filtering on subcategories"
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
# TODO: time_to_first_response
|
||
|
||
describe "topics_with_no_response" do
|
||
let(:report) { Report.find("topics_with_no_response") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with data" do
|
||
include_examples "with data x/y"
|
||
|
||
before(:each) do
|
||
user = Fabricate(:user)
|
||
Fabricate(:topic, category: category_2, user: user)
|
||
Fabricate(:post, topic: Fabricate(:topic, user: user), user: user)
|
||
Fabricate(:topic, user: user)
|
||
Fabricate(:topic, created_at: 45.days.ago, user: user)
|
||
end
|
||
|
||
context "with category filtering" do
|
||
let(:report) do
|
||
Report.find("topics_with_no_response", filters: { category: category_2.id })
|
||
end
|
||
|
||
include_examples "category filtering"
|
||
|
||
context "with subcategories" do
|
||
let(:report) do
|
||
Report.find(
|
||
"topics_with_no_response",
|
||
filters: {
|
||
category: category_1.id,
|
||
include_subcategories: true,
|
||
},
|
||
)
|
||
end
|
||
|
||
include_examples "category filtering on subcategories"
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "likes" do
|
||
let(:report) { Report.find("likes") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with data" do
|
||
include_examples "with data x/y"
|
||
|
||
before(:each) do
|
||
topic = Fabricate(:topic, category: category_2)
|
||
post = Fabricate(:post, topic: topic)
|
||
PostActionCreator.like(Fabricate(:user), post)
|
||
|
||
topic = Fabricate(:topic, category: category_3)
|
||
post = Fabricate(:post, topic: topic)
|
||
PostActionCreator.like(Fabricate(:user), post)
|
||
PostActionCreator.like(Fabricate(:user), post)
|
||
PostActionCreator
|
||
.like(Fabricate(:user), post)
|
||
.post_action
|
||
.tap { |pa| pa.created_at = 45.days.ago }
|
||
.save!
|
||
end
|
||
|
||
context "with category filtering" do
|
||
let(:report) { Report.find("likes", filters: { category: category_2.id }) }
|
||
|
||
include_examples "category filtering"
|
||
|
||
context "with subcategories" do
|
||
let(:report) do
|
||
Report.find("likes", filters: { category: category_1.id, include_subcategories: true })
|
||
end
|
||
|
||
include_examples "category filtering on subcategories"
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "user_flagging_ratio" do
|
||
let(:joffrey) { Fabricate(:user, username: "joffrey", refresh_auto_groups: true) }
|
||
let(:robin) { Fabricate(:user, username: "robin", refresh_auto_groups: true) }
|
||
let(:moderator) { Fabricate(:moderator) }
|
||
let(:user) { Fabricate(:user) }
|
||
|
||
context "with data" do
|
||
it "it works" do
|
||
topic = Fabricate(:topic, user: user)
|
||
2.times do
|
||
post_disagreed = Fabricate(:post, topic: topic, user: user)
|
||
result = PostActionCreator.spam(joffrey, post_disagreed)
|
||
result.reviewable.perform(moderator, :disagree)
|
||
end
|
||
|
||
3.times do
|
||
post_disagreed = Fabricate(:post, topic: topic, user: user)
|
||
result = PostActionCreator.spam(robin, post_disagreed)
|
||
result.reviewable.perform(moderator, :disagree)
|
||
end
|
||
post_agreed = Fabricate(:post, user: user, topic: topic)
|
||
result = PostActionCreator.off_topic(robin, post_agreed)
|
||
result.reviewable.perform(moderator, :agree_and_keep)
|
||
|
||
report = Report.find("user_flagging_ratio")
|
||
|
||
first = report.data[0]
|
||
expect(first[:username]).to eq("joffrey")
|
||
expect(first[:score]).to eq(2)
|
||
expect(first[:agreed_flags]).to eq(0)
|
||
expect(first[:disagreed_flags]).to eq(2)
|
||
|
||
second = report.data[1]
|
||
expect(second[:username]).to eq("robin")
|
||
expect(second[:agreed_flags]).to eq(1)
|
||
expect(second[:disagreed_flags]).to eq(3)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "report_suspicious_logins" do
|
||
let(:joffrey) { Fabricate(:user, username: "joffrey") }
|
||
let(:robin) { Fabricate(:user, username: "robin") }
|
||
|
||
context "with data" do
|
||
it "works" do
|
||
SiteSetting.verbose_auth_token_logging = true
|
||
|
||
UserAuthToken.log(action: "suspicious", user_id: joffrey.id, created_at: 2.hours.ago)
|
||
UserAuthToken.log(action: "suspicious", user_id: joffrey.id, created_at: 3.hours.ago)
|
||
UserAuthToken.log(action: "suspicious", user_id: robin.id, created_at: 1.hour.ago)
|
||
|
||
report = Report.find("suspicious_logins")
|
||
|
||
expect(report.data.length).to eq(3)
|
||
expect(report.data[0][:username]).to eq("robin")
|
||
expect(report.data[1][:username]).to eq("joffrey")
|
||
expect(report.data[2][:username]).to eq("joffrey")
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "report_staff_logins" do
|
||
let(:joffrey) { Fabricate(:admin, username: "joffrey") }
|
||
let(:robin) { Fabricate(:admin, username: "robin") }
|
||
let(:james) { Fabricate(:user, username: "james") }
|
||
|
||
context "with data" do
|
||
it "works" do
|
||
freeze_time_safe
|
||
|
||
ip = [81, 2, 69, 142]
|
||
|
||
DiscourseIpInfo.open_db(File.join(Rails.root, "spec", "fixtures", "mmdb"))
|
||
Resolv::DNS
|
||
.any_instance
|
||
.stubs(:getname)
|
||
.with(ip.join("."))
|
||
.returns("ip-#{ip.join("-")}.example.com")
|
||
|
||
UserAuthToken.log(
|
||
action: "generate",
|
||
user_id: robin.id,
|
||
client_ip: ip.join("."),
|
||
created_at: 1.hour.ago,
|
||
)
|
||
UserAuthToken.log(action: "generate", user_id: joffrey.id, client_ip: "1.2.3.4")
|
||
UserAuthToken.log(
|
||
action: "generate",
|
||
user_id: joffrey.id,
|
||
client_ip: ip.join("."),
|
||
created_at: 2.hours.ago,
|
||
)
|
||
UserAuthToken.log(action: "generate", user_id: james.id)
|
||
|
||
report = Report.find("staff_logins")
|
||
|
||
expect(report.data.length).to eq(3)
|
||
expect(report.data[0][:username]).to eq("joffrey")
|
||
|
||
expect(report.data[1][:username]).to eq("robin")
|
||
expect(report.data[1][:location]).to eq("London, England, United Kingdom")
|
||
|
||
expect(report.data[2][:username]).to eq("joffrey")
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "report_top_uploads" do
|
||
let(:report) { Report.find("top_uploads") }
|
||
let(:tarek) { Fabricate(:admin, username: "tarek") }
|
||
let(:khalil) { Fabricate(:admin, username: "khalil") }
|
||
|
||
context "with data" do
|
||
let!(:tarek_upload) do
|
||
Fabricate(
|
||
:upload,
|
||
user: tarek,
|
||
url: "/uploads/default/original/1X/tarek.jpg",
|
||
extension: "jpg",
|
||
original_filename: "tarek.jpg",
|
||
filesize: 1000,
|
||
)
|
||
end
|
||
let!(:khalil_upload) do
|
||
Fabricate(
|
||
:upload,
|
||
user: khalil,
|
||
url: "/uploads/default/original/1X/khalil.png",
|
||
extension: "png",
|
||
original_filename: "khalil.png",
|
||
filesize: 2000,
|
||
)
|
||
end
|
||
|
||
it "works" do
|
||
expect(report.data.length).to eq(2)
|
||
expect_uploads_report_data_to_be_equal(report.data, khalil, khalil_upload)
|
||
expect_uploads_report_data_to_be_equal(report.data, tarek, tarek_upload)
|
||
end
|
||
end
|
||
|
||
def expect_uploads_report_data_to_be_equal(data, user, upload)
|
||
row = data.find { |r| r[:author_id] == user.id }
|
||
expect(row[:author_id]).to eq(user.id)
|
||
expect(row[:author_username]).to eq(user.username)
|
||
expect(row[:author_avatar_template]).to eq(
|
||
User.avatar_template(user.username, user.uploaded_avatar_id),
|
||
)
|
||
expect(row[:filesize]).to eq(upload.filesize)
|
||
expect(row[:extension]).to eq(upload.extension)
|
||
expect(row[:file_url]).to eq(Discourse.store.cdn_url(upload.url))
|
||
expect(row[:file_name]).to eq(upload.original_filename.truncate(25))
|
||
end
|
||
|
||
include_examples "no data"
|
||
end
|
||
|
||
describe "report_top_ignored_users" do
|
||
let(:report) { Report.find("top_ignored_users") }
|
||
let(:tarek) { Fabricate(:user, username: "tarek") }
|
||
let(:john) { Fabricate(:user, username: "john") }
|
||
let(:matt) { Fabricate(:user, username: "matt") }
|
||
|
||
context "with data" do
|
||
before do
|
||
Fabricate(:ignored_user, user: tarek, ignored_user: john)
|
||
Fabricate(:ignored_user, user: tarek, ignored_user: matt)
|
||
end
|
||
|
||
it "works" do
|
||
expect(report.data.length).to eq(2)
|
||
|
||
expect_ignored_users_report_data_to_be_equal(report.data, john, 1, 0)
|
||
expect_ignored_users_report_data_to_be_equal(report.data, matt, 1, 0)
|
||
end
|
||
|
||
context "when muted users exist" do
|
||
before do
|
||
Fabricate(:muted_user, user: tarek, muted_user: john)
|
||
Fabricate(:muted_user, user: tarek, muted_user: matt)
|
||
end
|
||
|
||
it "works" do
|
||
expect(report.data.length).to eq(2)
|
||
expect_ignored_users_report_data_to_be_equal(report.data, john, 1, 1)
|
||
expect_ignored_users_report_data_to_be_equal(report.data, matt, 1, 1)
|
||
end
|
||
end
|
||
end
|
||
|
||
def expect_ignored_users_report_data_to_be_equal(data, user, ignores, mutes)
|
||
row = data.find { |r| r[:ignored_user_id] == user.id }
|
||
expect(row).to be_present
|
||
expect(row[:ignored_user_id]).to eq(user.id)
|
||
expect(row[:ignored_username]).to eq(user.username)
|
||
expect(row[:ignored_user_avatar_template]).to eq(
|
||
User.avatar_template(user.username, user.uploaded_avatar_id),
|
||
)
|
||
expect(row[:ignores_count]).to eq(ignores)
|
||
expect(row[:mutes_count]).to eq(mutes)
|
||
end
|
||
|
||
include_examples "no data"
|
||
end
|
||
|
||
describe "consolidated_page_views_browser_detection" do
|
||
before do
|
||
freeze_time(Time.now.at_midnight)
|
||
Theme.clear_default!
|
||
end
|
||
|
||
let(:reports) { Report.find("consolidated_page_views_browser_detection") }
|
||
|
||
context "with no data" do
|
||
it "works" do
|
||
reports.data.each { |report| expect(report[:data]).to be_empty }
|
||
end
|
||
end
|
||
|
||
context "with data" do
|
||
before do
|
||
CachedCounting.reset
|
||
CachedCounting.enable
|
||
ApplicationRequest.enable
|
||
SiteSetting.use_legacy_pageviews = true
|
||
end
|
||
|
||
after do
|
||
CachedCounting.reset
|
||
ApplicationRequest.disable
|
||
CachedCounting.disable
|
||
end
|
||
|
||
it "works" do
|
||
3.times { ApplicationRequest.increment!(:page_view_crawler) }
|
||
8.times { ApplicationRequest.increment!(:page_view_logged_in) }
|
||
6.times { ApplicationRequest.increment!(:page_view_logged_in_browser) }
|
||
2.times { ApplicationRequest.increment!(:page_view_anon) }
|
||
1.times { ApplicationRequest.increment!(:page_view_anon_browser) }
|
||
|
||
CachedCounting.flush
|
||
|
||
page_view_crawler_report = reports.data.find { |r| r[:req] == "page_view_crawler" }
|
||
page_view_logged_in_browser_report =
|
||
reports.data.find { |r| r[:req] == "page_view_logged_in_browser" }
|
||
page_view_anon_browser_report =
|
||
reports.data.find { |r| r[:req] == "page_view_anon_browser" }
|
||
page_view_other_report = reports.data.find { |r| r[:req] == "page_view_other" }
|
||
|
||
expect(page_view_crawler_report[:data][0][:y]).to eql(3)
|
||
expect(page_view_logged_in_browser_report[:data][0][:y]).to eql(6)
|
||
expect(page_view_anon_browser_report[:data][0][:y]).to eql(1)
|
||
expect(page_view_other_report[:data][0][:y]).to eql(3)
|
||
end
|
||
|
||
it "gives the same total as page_view_total_reqs" do
|
||
3.times { ApplicationRequest.increment!(:page_view_crawler) }
|
||
8.times { ApplicationRequest.increment!(:page_view_logged_in) }
|
||
6.times { ApplicationRequest.increment!(:page_view_logged_in_browser) }
|
||
2.times { ApplicationRequest.increment!(:page_view_anon) }
|
||
1.times { ApplicationRequest.increment!(:page_view_anon_browser) }
|
||
|
||
CachedCounting.flush
|
||
|
||
total_consolidated = reports.data.sum { |r| r[:data][0][:y] }
|
||
total_page_views = Report.find("page_view_total_reqs").data[0][:y]
|
||
|
||
expect(total_consolidated).to eq(total_page_views)
|
||
end
|
||
|
||
it "does not include any data before the first recorded browser page view (anon or logged in)" do
|
||
freeze_time DateTime.parse("2024-02-10")
|
||
|
||
3.times { ApplicationRequest.increment!(:page_view_logged_in) }
|
||
2.times { ApplicationRequest.increment!(:page_view_anon) }
|
||
|
||
CachedCounting.flush
|
||
|
||
freeze_time DateTime.parse("2024-03-10")
|
||
|
||
3.times { ApplicationRequest.increment!(:page_view_logged_in) }
|
||
2.times { ApplicationRequest.increment!(:page_view_anon) }
|
||
|
||
CachedCounting.flush
|
||
|
||
freeze_time DateTime.parse("2024-04-10")
|
||
|
||
3.times { ApplicationRequest.increment!(:page_view_crawler) }
|
||
8.times { ApplicationRequest.increment!(:page_view_logged_in) }
|
||
6.times { ApplicationRequest.increment!(:page_view_logged_in_browser) }
|
||
2.times { ApplicationRequest.increment!(:page_view_anon) }
|
||
1.times { ApplicationRequest.increment!(:page_view_anon_browser) }
|
||
|
||
CachedCounting.flush
|
||
|
||
report_in_range =
|
||
Report.find(
|
||
"consolidated_page_views_browser_detection",
|
||
start_date: DateTime.parse("2024-02-10").beginning_of_day,
|
||
end_date: DateTime.parse("2024-04-11").beginning_of_day,
|
||
)
|
||
|
||
page_view_crawler_report = report_in_range.data.find { |r| r[:req] == "page_view_crawler" }
|
||
page_view_logged_in_browser_report =
|
||
report_in_range.data.find { |r| r[:req] == "page_view_logged_in_browser" }
|
||
page_view_anon_browser_report =
|
||
report_in_range.data.find { |r| r[:req] == "page_view_anon_browser" }
|
||
page_view_other_report = report_in_range.data.find { |r| r[:req] == "page_view_other" }
|
||
|
||
expect(page_view_crawler_report[:data].sum { |d| d[:y] }).to eql(3)
|
||
expect(page_view_logged_in_browser_report[:data].sum { |d| d[:y] }).to eql(6)
|
||
expect(page_view_anon_browser_report[:data].sum { |d| d[:y] }).to eql(1)
|
||
expect(page_view_other_report[:data].sum { |d| d[:y] }).to eql(3)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "consolidated_page_views" do
|
||
before do
|
||
freeze_time(Time.now.at_midnight)
|
||
Theme.clear_default!
|
||
end
|
||
|
||
let(:reports) { Report.find("consolidated_page_views") }
|
||
|
||
context "with no data" do
|
||
it "works" do
|
||
reports.data.each { |report| expect(report[:data]).to be_empty }
|
||
end
|
||
end
|
||
|
||
context "with data" do
|
||
before do
|
||
CachedCounting.reset
|
||
CachedCounting.enable
|
||
ApplicationRequest.enable
|
||
end
|
||
|
||
after do
|
||
CachedCounting.reset
|
||
ApplicationRequest.disable
|
||
CachedCounting.disable
|
||
end
|
||
|
||
it "works" do
|
||
3.times { ApplicationRequest.increment!(:page_view_crawler) }
|
||
2.times { ApplicationRequest.increment!(:page_view_logged_in) }
|
||
ApplicationRequest.increment!(:page_view_anon)
|
||
|
||
CachedCounting.flush
|
||
|
||
page_view_crawler_report = reports.data.find { |r| r[:req] == "page_view_crawler" }
|
||
page_view_logged_in_report = reports.data.find { |r| r[:req] == "page_view_logged_in" }
|
||
page_view_anon_report = reports.data.find { |r| r[:req] == "page_view_anon" }
|
||
|
||
expect(page_view_crawler_report[:color]).to eql("#721D8D")
|
||
expect(page_view_crawler_report[:data][0][:y]).to eql(3)
|
||
|
||
expect(page_view_logged_in_report[:color]).to eql("#1EB8D1")
|
||
expect(page_view_logged_in_report[:data][0][:y]).to eql(2)
|
||
|
||
expect(page_view_anon_report[:color]).to eql("#9BC53D")
|
||
expect(page_view_anon_report[:data][0][:y]).to eql(1)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe ".report_consolidated_api_requests" do
|
||
before do
|
||
freeze_time(Time.now.at_midnight)
|
||
Theme.clear_default!
|
||
end
|
||
|
||
let(:reports) { Report.find("consolidated_api_requests") }
|
||
|
||
context "with no data" do
|
||
it "works" do
|
||
reports.data.each { |report| expect(report[:data]).to be_empty }
|
||
end
|
||
end
|
||
|
||
context "with data" do
|
||
before do
|
||
CachedCounting.reset
|
||
CachedCounting.enable
|
||
ApplicationRequest.enable
|
||
end
|
||
|
||
after do
|
||
ApplicationRequest.disable
|
||
CachedCounting.disable
|
||
end
|
||
|
||
it "works" do
|
||
2.times { ApplicationRequest.increment!(:api) }
|
||
ApplicationRequest.increment!(:user_api)
|
||
|
||
CachedCounting.flush
|
||
|
||
api_report = reports.data.find { |r| r[:req] == "api" }
|
||
user_api_report = reports.data.find { |r| r[:req] == "user_api" }
|
||
|
||
expect(api_report[:color]).to eql("#1EB8D1")
|
||
expect(api_report[:data][0][:y]).to eql(2)
|
||
|
||
expect(user_api_report[:color]).to eql("#9BC53D")
|
||
expect(user_api_report[:data][0][:y]).to eql(1)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "trust_level_growth" do
|
||
before do
|
||
freeze_time(Time.now.at_midnight)
|
||
Theme.clear_default!
|
||
end
|
||
|
||
let(:reports) { Report.find("trust_level_growth") }
|
||
|
||
context "with no data" do
|
||
it "works" do
|
||
reports.data.each { |report| expect(report[:data]).to be_empty }
|
||
end
|
||
end
|
||
|
||
context "with data" do
|
||
fab!(:gwen) { Fabricate(:user) }
|
||
fab!(:martin) { Fabricate(:user) }
|
||
|
||
before do
|
||
UserHistory.create(
|
||
action: UserHistory.actions[:auto_trust_level_change],
|
||
target_user_id: gwen.id,
|
||
new_value: TrustLevel[2],
|
||
previous_value: 1,
|
||
)
|
||
UserHistory.create(
|
||
action: UserHistory.actions[:change_trust_level],
|
||
target_user_id: martin.id,
|
||
new_value: TrustLevel[4],
|
||
previous_value: 0,
|
||
)
|
||
end
|
||
|
||
it "works" do
|
||
tl1_reached = reports.data.find { |r| r[:req] == "tl1_reached" }
|
||
tl2_reached = reports.data.find { |r| r[:req] == "tl2_reached" }
|
||
tl3_reached = reports.data.find { |r| r[:req] == "tl3_reached" }
|
||
tl4_reached = reports.data.find { |r| r[:req] == "tl4_reached" }
|
||
|
||
expect(tl1_reached[:data][0][:y]).to eql(0)
|
||
expect(tl2_reached[:data][0][:y]).to eql(1)
|
||
expect(tl3_reached[:data][0][:y]).to eql(0)
|
||
expect(tl4_reached[:data][0][:y]).to eql(1)
|
||
end
|
||
end
|
||
end
|
||
|
||
describe ".cache" do
|
||
let(:exception_report) { Report.find("exception_test", wrap_exceptions_in_test: true) }
|
||
let(:valid_report) { Report.find("valid_test", wrap_exceptions_in_test: true) }
|
||
|
||
before(:each) do
|
||
class Report
|
||
def self.report_exception_test(report)
|
||
report.data = x
|
||
end
|
||
|
||
def self.report_valid_test(report)
|
||
report.data = "success!"
|
||
end
|
||
end
|
||
end
|
||
|
||
it "caches exception reports for 1 minute" do
|
||
Discourse
|
||
.cache
|
||
.expects(:write)
|
||
.with(Report.cache_key(exception_report), exception_report.as_json, expires_in: 1.minute)
|
||
Report.cache(exception_report)
|
||
end
|
||
|
||
it "caches valid reports for 35 minutes" do
|
||
Discourse
|
||
.cache
|
||
.expects(:write)
|
||
.with(Report.cache_key(valid_report), valid_report.as_json, expires_in: 35.minutes)
|
||
Report.cache(valid_report)
|
||
end
|
||
end
|
||
|
||
describe "top_uploads" do
|
||
context "with no data" do
|
||
it "works" do
|
||
report = Report.find("top_uploads")
|
||
|
||
expect(report.data).to be_empty
|
||
end
|
||
end
|
||
|
||
context "with data" do
|
||
fab!(:jpg_upload) { Fabricate(:upload, extension: :jpg) }
|
||
fab!(:png_upload) { Fabricate(:upload, extension: :png) }
|
||
|
||
it "works" do
|
||
report = Report.find("top_uploads")
|
||
|
||
expect(report.data.length).to eq(2)
|
||
expect(report.data.map { |row| row[:extension] }).to contain_exactly("jpg", "png")
|
||
end
|
||
|
||
it "works with filters" do
|
||
report = Report.find("top_uploads", filters: { file_extension: "jpg" })
|
||
|
||
expect(report.data.length).to eq(1)
|
||
expect(report.data[0][:extension]).to eq("jpg")
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "top_users_by_likes_received" do
|
||
let(:report) { Report.find("top_users_by_likes_received") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with data" do
|
||
before do
|
||
user_1 = Fabricate(:user, username: "jonah")
|
||
user_2 = Fabricate(:user, username: "jake")
|
||
user_3 = Fabricate(:user, username: "john")
|
||
|
||
3.times { UserAction.create!(user_id: user_1.id, action_type: UserAction::WAS_LIKED) }
|
||
9.times { UserAction.create!(user_id: user_2.id, action_type: UserAction::WAS_LIKED) }
|
||
6.times { UserAction.create!(user_id: user_3.id, action_type: UserAction::WAS_LIKED) }
|
||
end
|
||
|
||
it "with category filtering" do
|
||
report = Report.find("top_users_by_likes_received")
|
||
|
||
expect(report.data.length).to eq(3)
|
||
expect(report.data[0][:username]).to eq("jake")
|
||
expect(report.data[1][:username]).to eq("john")
|
||
expect(report.data[2][:username]).to eq("jonah")
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "top_users_by_likes_received_from_a_variety_of_people" do
|
||
let(:report) { Report.find("top_users_by_likes_received_from_a_variety_of_people") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with data" do
|
||
before do
|
||
user_1 = Fabricate(:user, username: "jonah")
|
||
user_2 = Fabricate(:user, username: "jake")
|
||
user_3 = Fabricate(:user, username: "john")
|
||
user_4 = Fabricate(:user, username: "joseph")
|
||
user_5 = Fabricate(:user, username: "joanne")
|
||
user_6 = Fabricate(:user, username: "jerome")
|
||
|
||
topic_1 = Fabricate(:topic, user: user_1)
|
||
topic_2 = Fabricate(:topic, user: user_2)
|
||
topic_3 = Fabricate(:topic, user: user_3)
|
||
|
||
post_1 = Fabricate(:post, topic: topic_1, user: user_1)
|
||
post_2 = Fabricate(:post, topic: topic_2, user: user_2)
|
||
post_3 = Fabricate(:post, topic: topic_3, user: user_3)
|
||
|
||
3.times do
|
||
UserAction.create!(
|
||
user_id: user_4.id,
|
||
target_post_id: post_1.id,
|
||
action_type: UserAction::LIKE,
|
||
)
|
||
end
|
||
6.times do
|
||
UserAction.create!(
|
||
user_id: user_5.id,
|
||
target_post_id: post_2.id,
|
||
action_type: UserAction::LIKE,
|
||
)
|
||
end
|
||
9.times do
|
||
UserAction.create!(
|
||
user_id: user_6.id,
|
||
target_post_id: post_3.id,
|
||
action_type: UserAction::LIKE,
|
||
)
|
||
end
|
||
end
|
||
|
||
it "with category filtering" do
|
||
report = Report.find("top_users_by_likes_received_from_a_variety_of_people")
|
||
|
||
expect(report.data.length).to eq(3)
|
||
expect(report.data[0][:username]).to eq("jonah")
|
||
expect(report.data[1][:username]).to eq("jake")
|
||
expect(report.data[2][:username]).to eq("john")
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "top_users_by_likes_received_from_inferior_trust_level" do
|
||
let(:report) { Report.find("top_users_by_likes_received_from_inferior_trust_level") }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with data" do
|
||
before do
|
||
user_1 = Fabricate(:user, username: "jonah", trust_level: 2)
|
||
user_2 = Fabricate(:user, username: "jake", trust_level: 2)
|
||
user_3 = Fabricate(:user, username: "john", trust_level: 2)
|
||
user_4 = Fabricate(:user, username: "joseph", trust_level: 1)
|
||
user_5 = Fabricate(:user, username: "joanne", trust_level: 1)
|
||
user_6 = Fabricate(:user, username: "jerome", trust_level: 2)
|
||
|
||
topic_1 = Fabricate(:topic, user: user_1)
|
||
topic_2 = Fabricate(:topic, user: user_2)
|
||
topic_3 = Fabricate(:topic, user: user_3)
|
||
|
||
post_1 = Fabricate(:post, topic: topic_1, user: user_1)
|
||
post_2 = Fabricate(:post, topic: topic_2, user: user_2)
|
||
post_3 = Fabricate(:post, topic: topic_3, user: user_3)
|
||
|
||
3.times do
|
||
UserAction.create!(
|
||
user_id: user_4.id,
|
||
target_post_id: post_1.id,
|
||
action_type: UserAction::LIKE,
|
||
)
|
||
end
|
||
6.times do
|
||
UserAction.create!(
|
||
user_id: user_5.id,
|
||
target_post_id: post_2.id,
|
||
action_type: UserAction::LIKE,
|
||
)
|
||
end
|
||
9.times do
|
||
UserAction.create!(
|
||
user_id: user_6.id,
|
||
target_post_id: post_3.id,
|
||
action_type: UserAction::LIKE,
|
||
)
|
||
end
|
||
end
|
||
|
||
it "with category filtering" do
|
||
report = Report.find("top_users_by_likes_received_from_inferior_trust_level")
|
||
|
||
expect(report.data.length).to eq(2)
|
||
expect(report.data[0][:username]).to eq("jake")
|
||
expect(report.data[1][:username]).to eq("jonah")
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "topic_view_stats" do
|
||
let(:report) { Report.find("topic_view_stats") }
|
||
|
||
fab!(:topic_1) { Fabricate(:topic) }
|
||
fab!(:topic_2) { Fabricate(:topic) }
|
||
|
||
include_examples "no data"
|
||
|
||
context "with data" do
|
||
before do
|
||
freeze_time_safe
|
||
|
||
Fabricate(
|
||
:topic_view_stat,
|
||
topic: topic_1,
|
||
anonymous_views: 4,
|
||
logged_in_views: 2,
|
||
viewed_at: Time.zone.now - 5.days,
|
||
)
|
||
Fabricate(
|
||
:topic_view_stat,
|
||
topic: topic_1,
|
||
anonymous_views: 5,
|
||
logged_in_views: 18,
|
||
viewed_at: Time.zone.now - 3.days,
|
||
)
|
||
Fabricate(
|
||
:topic_view_stat,
|
||
topic: topic_2,
|
||
anonymous_views: 14,
|
||
logged_in_views: 21,
|
||
viewed_at: Time.zone.now - 5.days,
|
||
)
|
||
Fabricate(
|
||
:topic_view_stat,
|
||
topic: topic_2,
|
||
anonymous_views: 9,
|
||
logged_in_views: 13,
|
||
viewed_at: Time.zone.now - 1.days,
|
||
)
|
||
Fabricate(
|
||
:topic_view_stat,
|
||
topic: Fabricate(:topic),
|
||
anonymous_views: 1,
|
||
logged_in_views: 34,
|
||
viewed_at: Time.zone.now - 40.days,
|
||
)
|
||
end
|
||
|
||
it "works" do
|
||
expect(report.data.length).to eq(2)
|
||
expect(report.data[0]).to include(
|
||
topic_id: topic_2.id,
|
||
topic_title: topic_2.title,
|
||
total_anonymous_views: 23,
|
||
total_logged_in_views: 34,
|
||
total_views: 57,
|
||
)
|
||
expect(report.data[1]).to include(
|
||
topic_id: topic_1.id,
|
||
topic_title: topic_1.title,
|
||
total_anonymous_views: 9,
|
||
total_logged_in_views: 20,
|
||
total_views: 29,
|
||
)
|
||
end
|
||
|
||
context "with category filtering" do
|
||
let(:report) { Report.find("topic_view_stats", filters: { category: category_1.id }) }
|
||
|
||
before { topic_1.update!(category: category_1) }
|
||
|
||
it "filters topics to that category" do
|
||
expect(report.data.length).to eq(1)
|
||
expect(report.data[0]).to include(
|
||
topic_id: topic_1.id,
|
||
topic_title: topic_1.title,
|
||
total_anonymous_views: 9,
|
||
total_logged_in_views: 20,
|
||
total_views: 29,
|
||
)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|