Skip to content

Commit be5798f

Browse files
authored
FIX: Standardise the definition of what a solution is (#352)
There are three locations where a user's solution query is defined - user summary - user card - user directory This PR updates the queries to use data from the new `SolvedTopics` table instead of the `UserActions` table. And adds testssssss
1 parent 9e9ac28 commit be5798f

File tree

5 files changed

+147
-20
lines changed

5 files changed

+147
-20
lines changed

lib/discourse_solved/user_summary_extension.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ module DiscourseSolved::UserSummaryExtension
44
extend ActiveSupport::Concern
55

66
def solved_count
7-
DiscourseSolved::SolvedTopic.where(accepter: @user).count
7+
DiscourseSolved::SolvedTopic
8+
.joins("JOIN posts ON posts.id = discourse_solved_solved_topics.answer_post_id")
9+
.where(posts: { user_id: @user.id })
10+
.count
811
end
912
end

plugin.rb

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -240,12 +240,10 @@ def self.skip_db?
240240
end
241241

242242
add_to_serializer(:user_card, :accepted_answers) do
243-
UserAction
244-
.where(user_id: object.id)
245-
.where(action_type: UserAction::SOLVED)
246-
.joins("JOIN topics ON topics.id = user_actions.target_topic_id")
247-
.where("topics.archetype <> ?", Archetype.private_message)
248-
.where("topics.deleted_at IS NULL")
243+
DiscourseSolved::SolvedTopic
244+
.joins(answer_post: :user, topic: {})
245+
.where(posts: { user_id: object.id, deleted_at: nil })
246+
.where(topics: { archetype: Archetype.default, deleted_at: nil })
249247
.count
250248
end
251249
add_to_serializer(:user_summary, :solved_count) { object.solved_count }
@@ -288,24 +286,23 @@ def self.skip_db?
288286

289287
query = <<~SQL
290288
WITH x AS (
291-
SELECT u.id user_id, COUNT(DISTINCT ua.id) AS solutions
292-
FROM users AS u
293-
LEFT JOIN user_actions AS ua
294-
ON ua.user_id = u.id
295-
AND ua.action_type = #{UserAction::SOLVED}
296-
AND COALESCE(ua.created_at, :since) > :since
289+
SELECT p.user_id, COUNT(DISTINCT st.id) AS solutions
290+
FROM discourse_solved_solved_topics AS st
291+
JOIN posts AS p
292+
ON p.id = st.answer_post_id
293+
AND COALESCE(p.created_at, :since) > :since
294+
AND p.deleted_at IS NULL
297295
JOIN topics AS t
298-
ON t.id = ua.target_topic_id
296+
ON t.id = st.topic_id
299297
AND t.archetype <> 'private_message'
300298
AND t.deleted_at IS NULL
301-
JOIN posts AS p
302-
ON p.id = ua.target_post_id
303-
AND p.deleted_at IS NULL
299+
JOIN users AS u
300+
ON u.id = p.user_id
304301
WHERE u.id > 0
305302
AND u.active
306303
AND u.silenced_till IS NULL
307304
AND u.suspended_till IS NULL
308-
GROUP BY u.id
305+
GROUP BY p.user_id
309306
)
310307
UPDATE directory_items di
311308
SET solutions = x.solutions

spec/models/directory_item_spec.rb

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# frozen_string_literal: true
2+
3+
describe DirectoryItem, type: :model do
4+
describe "Updating user directory with solutions count" do
5+
fab!(:user)
6+
fab!(:admin)
7+
8+
fab!(:topic1) { Fabricate(:topic, archetype: "regular", user:) }
9+
fab!(:topic_post1) { Fabricate(:post, topic: topic1, user:) }
10+
11+
fab!(:topic2) { Fabricate(:topic, archetype: "regular", user:) }
12+
fab!(:topic_post2) { Fabricate(:post, topic: topic2, user:) }
13+
14+
fab!(:pm) { Fabricate(:topic, archetype: "private_message", user:, category_id: nil) }
15+
fab!(:pm_post) { Fabricate(:post, topic: pm, user:) }
16+
17+
before { SiteSetting.solved_enabled = true }
18+
19+
it "excludes PM post solutions from solutions" do
20+
DiscourseSolved.accept_answer!(topic_post1, admin)
21+
DiscourseSolved.accept_answer!(pm_post, admin)
22+
23+
DirectoryItem.refresh!
24+
25+
expect(
26+
DirectoryItem.find_by(
27+
user_id: user.id,
28+
period_type: DirectoryItem.period_types[:all],
29+
).solutions,
30+
).to eq(1)
31+
end
32+
33+
it "excludes deleted posts from solutions" do
34+
DiscourseSolved.accept_answer!(topic_post1, admin)
35+
DiscourseSolved.accept_answer!(topic_post2, admin)
36+
topic_post2.update(deleted_at: Time.zone.now)
37+
38+
DirectoryItem.refresh!
39+
40+
expect(
41+
DirectoryItem.find_by(
42+
user_id: user.id,
43+
period_type: DirectoryItem.period_types[:all],
44+
).solutions,
45+
).to eq(1)
46+
end
47+
48+
it "excludes deleted topics from solutions" do
49+
DiscourseSolved.accept_answer!(topic_post1, admin)
50+
DiscourseSolved.accept_answer!(topic_post2, admin)
51+
topic2.update(deleted_at: Time.zone.now)
52+
53+
DirectoryItem.refresh!
54+
55+
expect(
56+
DirectoryItem.find_by(
57+
user_id: user.id,
58+
period_type: DirectoryItem.period_types[:all],
59+
).solutions,
60+
).to eq(1)
61+
end
62+
63+
it "excludes solutions for silenced users" do
64+
user.update(silenced_till: Time.zone.now + 1.day)
65+
66+
DiscourseSolved.accept_answer!(topic_post1, admin)
67+
68+
DirectoryItem.refresh!
69+
70+
expect(
71+
DirectoryItem.find_by(
72+
user_id: user.id,
73+
period_type: DirectoryItem.period_types[:all],
74+
)&.solutions,
75+
).to eq(nil)
76+
end
77+
78+
it "excludes solutions for suspended users" do
79+
DiscourseSolved.accept_answer!(topic_post1, admin)
80+
user.update(suspended_till: Time.zone.now + 1.day)
81+
82+
DirectoryItem.refresh!
83+
84+
expect(
85+
DirectoryItem.find_by(
86+
user_id: user.id,
87+
period_type: DirectoryItem.period_types[:all],
88+
)&.solutions,
89+
).to eq(0)
90+
end
91+
92+
it "includes solutions for active users" do
93+
DiscourseSolved.accept_answer!(topic_post1, admin)
94+
95+
DirectoryItem.refresh!
96+
97+
expect(
98+
DirectoryItem.find_by(
99+
user_id: user.id,
100+
period_type: DirectoryItem.period_types[:all],
101+
).solutions,
102+
).to eq(1)
103+
end
104+
end
105+
end

spec/models/user_summary_spec.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
describe UserSummary do
4+
describe "solved_count" do
5+
it "indicates the number of times a user's post is a topic's solution" do
6+
topic = Fabricate(:topic)
7+
Fabricate(:post, topic:)
8+
user = Fabricate(:user)
9+
admin = Fabricate(:admin)
10+
post = Fabricate(:post, topic:, user:)
11+
12+
user_summary = UserSummary.new(user, Guardian.new)
13+
admin_summary = UserSummary.new(admin, Guardian.new)
14+
15+
expect(user_summary.solved_count).to eq(0)
16+
expect(admin_summary.solved_count).to eq(0)
17+
18+
DiscourseSolved.accept_answer!(post, admin)
19+
20+
expect(user_summary.solved_count).to eq(1)
21+
expect(admin_summary.solved_count).to eq(0)
22+
end
23+
end
24+
end

spec/serializers/user_card_serializer_spec.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# frozen_string_literal: true
22

3-
require "rails_helper"
4-
53
describe UserCardSerializer do
64
let(:user) { Fabricate(:user) }
75
let(:serializer) { described_class.new(user, scope: Guardian.new, root: false) }

0 commit comments

Comments
 (0)