discourse/plugins/discourse-solved/app/services/discourse_solved/shared_issue/toggle.rb
David Battersby 9a3f8569d8
FEATURE: add solved me too button (#39652)
Members can share that they’re experiencing a reported issue so that
admins can see how pervasive an issue is and members can opt into
hearing about the issue’s solution.

### Acceptance criteria

- When viewing an unsolved topic in a support category, members can
click or tap a Me too (N) button to indicate that they are also
experiencing this issue.
- The (N) indicates how many people have already clicked this button. It
should start at 1 to represent the topic author.
- When a topic has a solution, hide the Me too (N) button.
- After clicking the button, the topic’s notification level will change
to Tracking (if not already Tracking or Watching).
- If an anonymous user clicks the Me too (N) button, they will be
prompted to log in.

### How it looks

The **topic author** will see the button which already includes them as
the initial user with the issue. The button has read-only state for the
OP user (no-op):

<img width="1562" height="494" alt="image"
src="https://github.com/user-attachments/assets/2006b95a-04a3-427f-8250-334949d4fb53"
/>


**Other signed in users** can toggle the button:

<img width="1332" height="482" alt="image"
src="https://github.com/user-attachments/assets/68ee4c9e-c87a-47a1-94aa-04d7eaaaacc5"
/>


Internal ref: /t/181764

---------

Co-authored-by: awesomerobot <kris.aubuchon@discourse.org>
2026-05-13 17:03:25 +04:00

85 lines
2.1 KiB
Ruby
Vendored

# frozen_string_literal: true
class DiscourseSolved::SharedIssue::Toggle
include Service::Base
params do
attribute :topic_id, :integer
validates :topic_id, presence: true
end
model :topic
policy :can_create_shared_issue
lock(:topic) do
model :existing_shared_issue, optional: true
transaction do
only_if(:existing_shared_issue_present?) { step :withdraw_shared_issue }
only_if(:existing_shared_issue_absent?) do
model :shared_issue, :create_shared_issue
only_if(:notification_level_below_tracking?) { step :start_tracking_topic }
end
end
end
step :publish_shared_issue_change
private
def fetch_topic(params:)
Topic.find_by(id: params.topic_id)
end
def can_create_shared_issue(guardian:, topic:)
guardian.can_create_shared_issue?(topic)
end
def fetch_existing_shared_issue(topic:, guardian:)
DiscourseSolved::SharedIssue.find_by(topic:, user: guardian.user)
end
def existing_shared_issue_present?(existing_shared_issue:)
existing_shared_issue.present?
end
def existing_shared_issue_absent?(existing_shared_issue:)
existing_shared_issue.blank?
end
def withdraw_shared_issue(existing_shared_issue:)
existing_shared_issue.destroy
end
def create_shared_issue(topic:, guardian:)
DiscourseSolved::SharedIssue.create(topic:, user: guardian.user)
end
def notification_level_below_tracking?(topic:, guardian:)
current_level =
TopicUser.get(topic, guardian.user)&.notification_level ||
TopicUser.notification_levels[:regular]
current_level < TopicUser.notification_levels[:tracking]
end
def start_tracking_topic(topic:, guardian:)
TopicUser.change(
guardian.user.id,
topic.id,
notification_level: TopicUser.notification_levels[:tracking],
)
end
def publish_shared_issue_change(topic:, existing_shared_issue:)
MessageBus.publish(
"/topic/#{topic.id}",
{
type: :shared_issue,
count: DiscourseSolved::SharedIssue.count_for(topic),
user_created_shared_issue: existing_shared_issue.blank?,
},
topic.secure_audience_publish_messages,
)
end
end