mirror of
https://github.com/discourse/discourse.git
synced 2024-11-24 04:31:56 +08:00
29fac1ac18
Figuring out what unread topics a user has is a very expensive operation over time. Users can easily accumulate 10s of thousands of tracking state rows (1 for every topic they ever visit) When figuring out what a user has that is unread we need to join the tracking state records to the topic table. This can very quickly lead to cases where you need to scan through the entire topic table. This commit optimises it so we always keep track of the "first" date a user has unread topics. Then we can easily filter out all earlier topics from the join. We use pg functions, instead of nested queries here to assist the planner.
132 lines
3.6 KiB
Ruby
132 lines
3.6 KiB
Ruby
require 'rails_helper'
|
|
|
|
describe UserStat do
|
|
|
|
it "is created automatically when a user is created" do
|
|
user = Fabricate(:evil_trout)
|
|
expect(user.user_stat).to be_present
|
|
|
|
# It populates the `new_since` field by default
|
|
expect(user.user_stat.new_since).to be_present
|
|
end
|
|
|
|
context "#update_first_topic_unread_at" do
|
|
it "updates date correctly for staff" do
|
|
now = Time.zone.now
|
|
|
|
admin = Fabricate(:admin)
|
|
topic = Fabricate(:topic,
|
|
highest_staff_post_number: 7,
|
|
highest_post_number: 1,
|
|
last_unread_at: now
|
|
)
|
|
|
|
UserStat.update_first_topic_unread_at!
|
|
|
|
admin.reload
|
|
|
|
expect(admin.user_stat.first_topic_unread_at).to_not be_within(5.years).of(now)
|
|
|
|
TopicUser.change(admin.id, topic.id, last_read_post_number: 1,
|
|
notification_level: NotificationLevels.all[:tracking])
|
|
|
|
UserStat.update_first_topic_unread_at!
|
|
|
|
admin.reload
|
|
|
|
expect(admin.user_stat.first_topic_unread_at).to be_within(1.second).of(now)
|
|
end
|
|
end
|
|
|
|
context '#update_view_counts' do
|
|
|
|
let(:user) { Fabricate(:user) }
|
|
let(:stat) { user.user_stat }
|
|
|
|
context 'topics_entered' do
|
|
context 'without any views' do
|
|
it "doesn't increase the user's topics_entered" do
|
|
expect { UserStat.update_view_counts; stat.reload }.not_to change(stat, :topics_entered)
|
|
end
|
|
end
|
|
|
|
context 'with a view' do
|
|
let(:topic) { Fabricate(:topic) }
|
|
let!(:view) { TopicViewItem.add(topic.id, '127.0.0.1', user.id) }
|
|
|
|
before do
|
|
user.update_column :last_seen_at, 1.second.ago
|
|
end
|
|
|
|
it "adds one to the topics entered" do
|
|
UserStat.update_view_counts
|
|
stat.reload
|
|
expect(stat.topics_entered).to eq(1)
|
|
end
|
|
|
|
it "won't record a second view as a different topic" do
|
|
TopicViewItem.add(topic.id, '127.0.0.1', user.id)
|
|
UserStat.update_view_counts
|
|
stat.reload
|
|
expect(stat.topics_entered).to eq(1)
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
context 'posts_read_count' do
|
|
context 'without any post timings' do
|
|
it "doesn't increase the user's posts_read_count" do
|
|
expect { UserStat.update_view_counts; stat.reload }.not_to change(stat, :posts_read_count)
|
|
end
|
|
end
|
|
|
|
context 'with a post timing' do
|
|
let!(:post) { Fabricate(:post) }
|
|
let!(:post_timings) do
|
|
PostTiming.record_timing(msecs: 1234, topic_id: post.topic_id, user_id: user.id, post_number: post.post_number)
|
|
end
|
|
|
|
before do
|
|
user.update_column :last_seen_at, 1.second.ago
|
|
end
|
|
|
|
it "increases posts_read_count" do
|
|
UserStat.update_view_counts
|
|
stat.reload
|
|
expect(stat.posts_read_count).to eq(1)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
describe 'update_time_read!' do
|
|
let(:user) { Fabricate(:user) }
|
|
let(:stat) { user.user_stat }
|
|
|
|
it 'makes no changes if nothing is cached' do
|
|
stat.expects(:last_seen_cached).returns(nil)
|
|
stat.update_time_read!
|
|
stat.reload
|
|
expect(stat.time_read).to eq(0)
|
|
end
|
|
|
|
it 'makes a change if time read is below threshold' do
|
|
stat.expects(:last_seen_cached).returns(Time.now - 10)
|
|
stat.update_time_read!
|
|
stat.reload
|
|
expect(stat.time_read).to eq(10)
|
|
end
|
|
|
|
it 'makes no change if time read is above threshold' do
|
|
t = Time.now - 1 - UserStat::MAX_TIME_READ_DIFF
|
|
stat.expects(:last_seen_cached).returns(t)
|
|
stat.update_time_read!
|
|
stat.reload
|
|
expect(stat.time_read).to eq(0)
|
|
end
|
|
|
|
end
|
|
end
|