FIX: Accepting another answer does not commit (#360)

When an answer already exists, clicking " Solution" on another post works, but does not commit.

This commit fixes that and also adds a test, and a transaction around accepting a solution (deleting the topic timer, previous user action, etc).
This commit is contained in:
Natalie Tay 2025-04-07 11:42:36 +08:00 committed by GitHub
parent 24d819a7d6
commit 6e12858bde
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 66 deletions

View File

@ -7,7 +7,7 @@ module DiscourseSolved
belongs_to :topic, class_name: "Topic" belongs_to :topic, class_name: "Topic"
belongs_to :answer_post, class_name: "Post", foreign_key: "answer_post_id" belongs_to :answer_post, class_name: "Post", foreign_key: "answer_post_id"
belongs_to :accepter, class_name: "User", foreign_key: "accepter_user_id" belongs_to :accepter, class_name: "User", foreign_key: "accepter_user_id"
belongs_to :topic_timer belongs_to :topic_timer, dependent: :destroy
validates :topic_id, presence: true validates :topic_id, presence: true
validates :answer_post_id, presence: true validates :answer_post_id, presence: true

View File

@ -33,11 +33,13 @@ after_initialize do
DistributedMutex.synchronize("discourse_solved_toggle_answer_#{topic.id}") do DistributedMutex.synchronize("discourse_solved_toggle_answer_#{topic.id}") do
solved = topic.solved solved = topic.solved
ActiveRecord::Base.transaction do
if previous_accepted_post_id = solved&.answer_post_id if previous_accepted_post_id = solved&.answer_post_id
UserAction.where( UserAction.where(
action_type: UserAction::SOLVED, action_type: UserAction::SOLVED,
target_post_id: previous_accepted_post_id, target_post_id: previous_accepted_post_id,
).destroy_all ).destroy_all
solved.destroy!
else else
UserAction.log_action!( UserAction.log_action!(
action_type: UserAction::SOLVED, action_type: UserAction::SOLVED,
@ -48,23 +50,21 @@ after_initialize do
) )
end end
solved ||= solved =
DiscourseSolved::SolvedTopic.new(topic:, answer_post: post, accepter: acting_user) DiscourseSolved::SolvedTopic.new(topic:, answer_post: post, accepter: acting_user)
notification_data = {
message: "solved.accepted_notification",
display_username: acting_user.username,
topic_title: topic.title,
title: "solved.notification.title",
}.to_json
unless acting_user.id == post.user_id unless acting_user.id == post.user_id
Notification.create!( Notification.create!(
notification_type: Notification.types[:custom], notification_type: Notification.types[:custom],
user_id: post.user_id, user_id: post.user_id,
topic_id: post.topic_id, topic_id: post.topic_id,
post_number: post.post_number, post_number: post.post_number,
data: notification_data, data: {
message: "solved.accepted_notification",
display_username: acting_user.username,
topic_title: topic.title,
title: "solved.notification.title",
}.to_json,
) )
end end
@ -74,7 +74,12 @@ after_initialize do
user_id: topic.user_id, user_id: topic.user_id,
topic_id: post.topic_id, topic_id: post.topic_id,
post_number: post.post_number, post_number: post.post_number,
data: notification_data, data: {
message: "solved.accepted_notification",
display_username: acting_user.username,
topic_title: topic.title,
title: "solved.notification.title",
}.to_json,
) )
end end
@ -100,6 +105,7 @@ after_initialize do
end end
solved.save! solved.save!
end
if WebHook.active_web_hooks(:accepted_solution).exists? if WebHook.active_web_hooks(:accepted_solution).exists?
payload = WebHook.generate_payload(:post, post) payload = WebHook.generate_payload(:post, post)
@ -127,7 +133,6 @@ after_initialize do
topic_id: post.topic_id, topic_id: post.topic_id,
post_number: post.post_number, post_number: post.post_number,
)&.destroy! )&.destroy!
solved.topic_timer.destroy! if solved.topic_timer
solved.destroy! solved.destroy!
end end

View File

@ -540,6 +540,26 @@ RSpec.describe "Managing Posts solved status" do
end end
end end
describe "#accept_answer!" do
it "marks the post as the accepted answer correctly" do
user = Fabricate(:user, trust_level: 1)
topic = Fabricate(:topic, user:)
reply1 = Fabricate(:post, topic:, user:, post_number: 2)
reply2 = Fabricate(:post, topic:, user:, post_number: 3)
DiscourseSolved.accept_answer!(reply1, user)
topic.reload
expect(topic.solved.answer_post_id).to eq(reply1.id)
expect(topic.solved.topic_timer).to eq(topic.public_topic_timer)
DiscourseSolved.accept_answer!(reply2, user)
topic.reload
expect(topic.solved.answer_post_id).to eq(reply2.id)
end
end
describe "user actions stream modifier" do describe "user actions stream modifier" do
it "correctly list solutions" do it "correctly list solutions" do
t1 = Fabricate(:topic) t1 = Fabricate(:topic)