mirror of
https://github.com/discourse/discourse-solved.git
synced 2025-03-09 14:37:15 +00:00
When using the `status:unsolved` search filter, the plugin was only returning results from topics in categories where solved was enabled. This commit changes the search query to also include topics with tags that have solved enabled via the `enable_solved_tags` site setting. This fixes the `status:unsolved tags:tag1` search query.
626 lines
22 KiB
Ruby
626 lines
22 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# name: discourse-solved
|
|
# about: Allows users to accept solutions on topics in designated categories.
|
|
# meta_topic_id: 30155
|
|
# version: 0.1
|
|
# authors: Sam Saffron
|
|
# url: https://github.com/discourse/discourse-solved
|
|
|
|
enabled_site_setting :solved_enabled
|
|
|
|
if respond_to?(:register_svg_icon)
|
|
register_svg_icon "far fa-check-square"
|
|
register_svg_icon "check-square"
|
|
register_svg_icon "far fa-square"
|
|
end
|
|
|
|
register_asset "stylesheets/solutions.scss"
|
|
register_asset "stylesheets/mobile/solutions.scss", :mobile
|
|
|
|
after_initialize do
|
|
module ::DiscourseSolved
|
|
PLUGIN_NAME = "discourse-solved"
|
|
AUTO_CLOSE_TOPIC_TIMER_CUSTOM_FIELD = "solved_auto_close_topic_timer_id"
|
|
ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD = "accepted_answer_post_id"
|
|
ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD = "enable_accepted_answers"
|
|
IS_ACCEPTED_ANSWER_CUSTOM_FIELD = "is_accepted_answer"
|
|
|
|
class Engine < ::Rails::Engine
|
|
engine_name PLUGIN_NAME
|
|
isolate_namespace DiscourseSolved
|
|
end
|
|
end
|
|
|
|
SeedFu.fixture_paths << Rails.root.join("plugins", "discourse-solved", "db", "fixtures").to_s
|
|
|
|
require_relative "app/controllers/answer_controller"
|
|
require_relative "app/lib/first_accepted_post_solution_validator"
|
|
require_relative "app/lib/accepted_answer_cache"
|
|
require_relative "app/lib/guardian_extensions"
|
|
require_relative "app/lib/before_head_close"
|
|
require_relative "app/lib/category_extension"
|
|
require_relative "app/lib/post_serializer_extension"
|
|
require_relative "app/lib/topic_posters_summary_extension"
|
|
require_relative "app/lib/topic_view_serializer_extension"
|
|
require_relative "app/lib/user_summary_extension"
|
|
require_relative "app/lib/web_hook_extension"
|
|
require_relative "app/serializers/concerns/topic_answer_mixin"
|
|
|
|
module ::DiscourseSolved
|
|
def self.accept_answer!(post, acting_user, topic: nil)
|
|
topic ||= post.topic
|
|
|
|
DistributedMutex.synchronize("discourse_solved_toggle_answer_#{topic.id}") do
|
|
accepted_id = topic.custom_fields[ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD].to_i
|
|
|
|
if accepted_id > 0
|
|
if p2 = Post.find_by(id: accepted_id)
|
|
p2.custom_fields.delete(IS_ACCEPTED_ANSWER_CUSTOM_FIELD)
|
|
p2.save!
|
|
|
|
if defined?(UserAction::SOLVED)
|
|
UserAction.where(action_type: UserAction::SOLVED, target_post_id: p2.id).destroy_all
|
|
end
|
|
end
|
|
end
|
|
|
|
post.custom_fields[IS_ACCEPTED_ANSWER_CUSTOM_FIELD] = "true"
|
|
topic.custom_fields[ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD] = post.id
|
|
|
|
if defined?(UserAction::SOLVED)
|
|
UserAction.log_action!(
|
|
action_type: UserAction::SOLVED,
|
|
user_id: post.user_id,
|
|
acting_user_id: acting_user.id,
|
|
target_post_id: post.id,
|
|
target_topic_id: post.topic_id,
|
|
)
|
|
end
|
|
|
|
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
|
|
Notification.create!(
|
|
notification_type: Notification.types[:custom],
|
|
user_id: post.user_id,
|
|
topic_id: post.topic_id,
|
|
post_number: post.post_number,
|
|
data: notification_data,
|
|
)
|
|
end
|
|
|
|
if SiteSetting.notify_on_staff_accept_solved && acting_user.id != topic.user_id
|
|
Notification.create!(
|
|
notification_type: Notification.types[:custom],
|
|
user_id: topic.user_id,
|
|
topic_id: post.topic_id,
|
|
post_number: post.post_number,
|
|
data: notification_data,
|
|
)
|
|
end
|
|
|
|
auto_close_hours = 0
|
|
if topic&.category.present?
|
|
auto_close_hours = topic.category.custom_fields["solved_topics_auto_close_hours"].to_i
|
|
auto_close_hours = 175_200 if auto_close_hours > 175_200 # 20 years
|
|
end
|
|
|
|
auto_close_hours = SiteSetting.solved_topics_auto_close_hours if auto_close_hours == 0
|
|
|
|
if (auto_close_hours > 0) && !topic.closed
|
|
topic_timer =
|
|
topic.set_or_create_timer(
|
|
TopicTimer.types[:silent_close],
|
|
nil,
|
|
based_on_last_post: true,
|
|
duration_minutes: auto_close_hours * 60,
|
|
)
|
|
|
|
topic.custom_fields[AUTO_CLOSE_TOPIC_TIMER_CUSTOM_FIELD] = topic_timer.id
|
|
|
|
MessageBus.publish("/topic/#{topic.id}", reload_topic: true)
|
|
end
|
|
|
|
topic.save!
|
|
post.save!
|
|
|
|
if WebHook.active_web_hooks(:accepted_solution).exists?
|
|
payload = WebHook.generate_payload(:post, post)
|
|
WebHook.enqueue_solved_hooks(:accepted_solution, post, payload)
|
|
end
|
|
|
|
DiscourseEvent.trigger(:accepted_solution, post)
|
|
end
|
|
end
|
|
|
|
def self.unaccept_answer!(post, topic: nil)
|
|
topic ||= post.topic
|
|
|
|
DistributedMutex.synchronize("discourse_solved_toggle_answer_#{topic.id}") do
|
|
post.custom_fields.delete(IS_ACCEPTED_ANSWER_CUSTOM_FIELD)
|
|
topic.custom_fields.delete(ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD)
|
|
|
|
if timer_id = topic.custom_fields[AUTO_CLOSE_TOPIC_TIMER_CUSTOM_FIELD]
|
|
topic_timer = TopicTimer.find_by(id: timer_id)
|
|
topic_timer.destroy! if topic_timer
|
|
topic.custom_fields.delete(AUTO_CLOSE_TOPIC_TIMER_CUSTOM_FIELD)
|
|
end
|
|
|
|
topic.save!
|
|
post.save!
|
|
|
|
# TODO remove_action! does not allow for this type of interface
|
|
if defined?(UserAction::SOLVED)
|
|
UserAction.where(action_type: UserAction::SOLVED, target_post_id: post.id).destroy_all
|
|
end
|
|
|
|
# yank notification
|
|
notification =
|
|
Notification.find_by(
|
|
notification_type: Notification.types[:custom],
|
|
user_id: post.user_id,
|
|
topic_id: post.topic_id,
|
|
post_number: post.post_number,
|
|
)
|
|
|
|
notification.destroy! if notification
|
|
|
|
if WebHook.active_web_hooks(:unaccepted_solution).exists?
|
|
payload = WebHook.generate_payload(:post, post)
|
|
WebHook.enqueue_solved_hooks(:unaccepted_solution, post, payload)
|
|
end
|
|
|
|
DiscourseEvent.trigger(:unaccepted_solution, post)
|
|
end
|
|
end
|
|
|
|
def self.skip_db?
|
|
defined?(GlobalSetting.skip_db?) && GlobalSetting.skip_db?
|
|
end
|
|
end
|
|
|
|
reloadable_patch do |plugin|
|
|
::Guardian.prepend(DiscourseSolved::GuardianExtensions)
|
|
::WebHook.prepend(DiscourseSolved::WebHookExtension)
|
|
::TopicViewSerializer.prepend(DiscourseSolved::TopicViewSerializerExtension)
|
|
::Category.prepend(DiscourseSolved::CategoryExtension)
|
|
::PostSerializer.prepend(DiscourseSolved::PostSerializerExtension)
|
|
::UserSummary.prepend(DiscourseSolved::UserSummaryExtension) if defined?(::UserAction::SOLVED)
|
|
if respond_to?(:register_topic_list_preload_user_ids)
|
|
::Topic.attr_accessor(:accepted_answer_user_id)
|
|
::TopicPostersSummary.alias_method(:old_user_ids, :user_ids)
|
|
::TopicPostersSummary.prepend(DiscourseSolved::TopicPostersSummaryExtension)
|
|
end
|
|
[
|
|
::TopicListItemSerializer,
|
|
::SearchTopicListItemSerializer,
|
|
::SuggestedTopicSerializer,
|
|
::UserSummarySerializer::TopicSerializer,
|
|
::ListableTopicSerializer,
|
|
].each { |klass| klass.include(TopicAnswerMixin) }
|
|
end
|
|
|
|
# we got to do a one time upgrade
|
|
if !::DiscourseSolved.skip_db? && defined?(UserAction::SOLVED)
|
|
unless Discourse.redis.get("solved_already_upgraded")
|
|
unless UserAction.where(action_type: UserAction::SOLVED).exists?
|
|
Rails.logger.info("Upgrading storage for solved")
|
|
sql = <<~SQL
|
|
INSERT INTO user_actions(action_type,
|
|
user_id,
|
|
target_topic_id,
|
|
target_post_id,
|
|
acting_user_id,
|
|
created_at,
|
|
updated_at)
|
|
SELECT :solved,
|
|
p.user_id,
|
|
p.topic_id,
|
|
p.id,
|
|
t.user_id,
|
|
pc.created_at,
|
|
pc.updated_at
|
|
FROM
|
|
post_custom_fields pc
|
|
JOIN
|
|
posts p ON p.id = pc.post_id
|
|
JOIN
|
|
topics t ON t.id = p.topic_id
|
|
WHERE
|
|
pc.name = 'is_accepted_answer' AND
|
|
pc.value = 'true' AND
|
|
p.user_id IS NOT NULL
|
|
SQL
|
|
|
|
DB.exec(sql, solved: UserAction::SOLVED)
|
|
end
|
|
Discourse.redis.set("solved_already_upgraded", "true")
|
|
end
|
|
end
|
|
|
|
DiscourseSolved::Engine.routes.draw do
|
|
post "/accept" => "answer#accept"
|
|
post "/unaccept" => "answer#unaccept"
|
|
end
|
|
|
|
Discourse::Application.routes.append { mount ::DiscourseSolved::Engine, at: "solution" }
|
|
|
|
on(:post_destroyed) do |post|
|
|
if post.custom_fields[::DiscourseSolved::IS_ACCEPTED_ANSWER_CUSTOM_FIELD] == "true"
|
|
::DiscourseSolved.unaccept_answer!(post)
|
|
end
|
|
end
|
|
|
|
add_api_key_scope(
|
|
:solved,
|
|
{ answer: { actions: %w[discourse_solved/answer#accept discourse_solved/answer#unaccept] } },
|
|
)
|
|
|
|
topic_view_post_custom_fields_allowlister { [::DiscourseSolved::IS_ACCEPTED_ANSWER_CUSTOM_FIELD] }
|
|
|
|
register_html_builder("server:before-head-close-crawler") do |controller|
|
|
DiscourseSolved::BeforeHeadClose.new(controller).html
|
|
end
|
|
|
|
register_html_builder("server:before-head-close") do |controller|
|
|
DiscourseSolved::BeforeHeadClose.new(controller).html
|
|
end
|
|
|
|
if Report.respond_to?(:add_report)
|
|
Report.add_report("accepted_solutions") do |report|
|
|
report.data = []
|
|
|
|
accepted_solutions =
|
|
TopicCustomField.where(name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD)
|
|
|
|
category_id, include_subcategories = report.add_category_filter
|
|
if category_id
|
|
if include_subcategories
|
|
accepted_solutions =
|
|
accepted_solutions.joins(:topic).where(
|
|
"topics.category_id IN (?)",
|
|
Category.subcategory_ids(category_id),
|
|
)
|
|
else
|
|
accepted_solutions =
|
|
accepted_solutions.joins(:topic).where("topics.category_id = ?", category_id)
|
|
end
|
|
end
|
|
|
|
accepted_solutions
|
|
.where("topic_custom_fields.created_at >= ?", report.start_date)
|
|
.where("topic_custom_fields.created_at <= ?", report.end_date)
|
|
.group("DATE(topic_custom_fields.created_at)")
|
|
.order("DATE(topic_custom_fields.created_at)")
|
|
.count
|
|
.each { |date, count| report.data << { x: date, y: count } }
|
|
report.total = accepted_solutions.count
|
|
report.prev30Days =
|
|
accepted_solutions
|
|
.where("topic_custom_fields.created_at >= ?", report.start_date - 30.days)
|
|
.where("topic_custom_fields.created_at <= ?", report.start_date)
|
|
.count
|
|
end
|
|
end
|
|
|
|
if respond_to?(:register_modifier)
|
|
register_modifier(:search_rank_sort_priorities) do |priorities, search|
|
|
if SiteSetting.prioritize_solved_topics_in_search
|
|
condition = <<~SQL
|
|
EXISTS
|
|
(
|
|
SELECT 1 FROM topic_custom_fields f
|
|
WHERE topics.id = f.topic_id
|
|
AND f.name = '#{::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD}'
|
|
)
|
|
SQL
|
|
|
|
priorities.push([condition, 1.1])
|
|
else
|
|
priorities
|
|
end
|
|
end
|
|
end
|
|
|
|
if defined?(::UserAction::SOLVED)
|
|
add_to_serializer(:user_summary, :solved_count) { object.solved_count }
|
|
end
|
|
add_to_serializer(:post, :can_accept_answer) do
|
|
scope.can_accept_answer?(topic, object) && object.post_number > 1 && !accepted_answer
|
|
end
|
|
add_to_serializer(:post, :can_unaccept_answer) do
|
|
scope.can_accept_answer?(topic, object) && accepted_answer
|
|
end
|
|
add_to_serializer(:post, :accepted_answer) do
|
|
post_custom_fields[::DiscourseSolved::IS_ACCEPTED_ANSWER_CUSTOM_FIELD] == "true"
|
|
end
|
|
add_to_serializer(:post, :topic_accepted_answer) do
|
|
topic&.custom_fields&.[](::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD).present?
|
|
end
|
|
|
|
#TODO Remove when plugin is 1.0
|
|
if Search.respond_to? :advanced_filter
|
|
Search.advanced_filter(/status:solved/) do |posts|
|
|
posts.where(
|
|
"topics.id IN (
|
|
SELECT tc.topic_id
|
|
FROM topic_custom_fields tc
|
|
WHERE tc.name = '#{::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD}' AND
|
|
tc.value IS NOT NULL
|
|
)",
|
|
)
|
|
end
|
|
|
|
Search.advanced_filter(/status:unsolved/) do |posts|
|
|
if SiteSetting.allow_solved_on_all_topics
|
|
posts.where(
|
|
"topics.id NOT IN (
|
|
SELECT tc.topic_id
|
|
FROM topic_custom_fields tc
|
|
WHERE tc.name = '#{::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD}' AND
|
|
tc.value IS NOT NULL
|
|
)",
|
|
)
|
|
else
|
|
tag_ids = Tag.where(name: SiteSetting.enable_solved_tags.split("|")).pluck(:id)
|
|
|
|
posts.where(
|
|
"topics.id NOT IN (
|
|
SELECT tc.topic_id
|
|
FROM topic_custom_fields tc
|
|
WHERE tc.name = '#{::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD}' AND
|
|
tc.value IS NOT NULL
|
|
) AND (topics.id IN (
|
|
SELECT top.id
|
|
FROM topics top
|
|
INNER JOIN category_custom_fields cc
|
|
ON top.category_id = cc.category_id
|
|
WHERE cc.name = '#{::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD}' AND
|
|
cc.value = 'true'
|
|
) OR topics.id IN (
|
|
SELECT top.id
|
|
FROM topics top
|
|
INNER JOIN topic_tags tt
|
|
ON top.id = tt.topic_id
|
|
WHERE tt.tag_id IN (?)
|
|
))",
|
|
tag_ids,
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
if Discourse.has_needed_version?(Discourse::VERSION::STRING, "1.8.0.beta6")
|
|
TopicQuery.add_custom_filter(:solved) do |results, topic_query|
|
|
if topic_query.options[:solved] == "yes"
|
|
results =
|
|
results.where(
|
|
"topics.id IN (
|
|
SELECT tc.topic_id
|
|
FROM topic_custom_fields tc
|
|
WHERE tc.name = '#{::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD}' AND
|
|
tc.value IS NOT NULL
|
|
)",
|
|
)
|
|
elsif topic_query.options[:solved] == "no"
|
|
results =
|
|
results.where(
|
|
"topics.id NOT IN (
|
|
SELECT tc.topic_id
|
|
FROM topic_custom_fields tc
|
|
WHERE tc.name = '#{::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD}' AND
|
|
tc.value IS NOT NULL
|
|
)",
|
|
)
|
|
end
|
|
results
|
|
end
|
|
end
|
|
|
|
if TopicList.respond_to? :preloaded_custom_fields
|
|
TopicList.preloaded_custom_fields << ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD
|
|
end
|
|
if Site.respond_to? :preloaded_category_custom_fields
|
|
Site.preloaded_category_custom_fields << ::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD
|
|
end
|
|
if Search.respond_to? :preloaded_topic_custom_fields
|
|
Search.preloaded_topic_custom_fields << ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD
|
|
end
|
|
|
|
if CategoryList.respond_to?(:preloaded_topic_custom_fields)
|
|
CategoryList.preloaded_topic_custom_fields << ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD
|
|
end
|
|
|
|
on(:filter_auto_bump_topics) { |_category, filters| filters.push(->(r) { r.where(<<~SQL) }) }
|
|
NOT EXISTS(
|
|
SELECT 1 FROM topic_custom_fields
|
|
WHERE topic_id = topics.id
|
|
AND name = '#{::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD}'
|
|
AND value IS NOT NULL
|
|
)
|
|
SQL
|
|
|
|
on(:before_post_publish_changes) do |post_changes, topic_changes, options|
|
|
category_id_changes = topic_changes.diff["category_id"].to_a
|
|
tag_changes = topic_changes.diff["tags"].to_a
|
|
|
|
old_allowed = Guardian.new.allow_accepted_answers?(category_id_changes[0], tag_changes[0])
|
|
new_allowed = Guardian.new.allow_accepted_answers?(category_id_changes[1], tag_changes[1])
|
|
|
|
options[:refresh_stream] = true if old_allowed != new_allowed
|
|
end
|
|
|
|
on(:after_populate_dev_records) do |records, type|
|
|
next unless SiteSetting.solved_enabled
|
|
|
|
if type == :category
|
|
next if SiteSetting.allow_solved_on_all_topics
|
|
|
|
solved_category =
|
|
DiscourseDev::Record.random(
|
|
Category.where(read_restricted: false, id: records.pluck(:id), parent_category_id: nil),
|
|
)
|
|
CategoryCustomField.create!(
|
|
category_id: solved_category.id,
|
|
name: ::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD,
|
|
value: "true",
|
|
)
|
|
puts "discourse-solved enabled on category '#{solved_category.name}' (#{solved_category.id})."
|
|
elsif type == :topic
|
|
topics = Topic.where(id: records.pluck(:id))
|
|
|
|
unless SiteSetting.allow_solved_on_all_topics
|
|
solved_category_id =
|
|
CategoryCustomField
|
|
.where(name: ::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD, value: "true")
|
|
.first
|
|
.category_id
|
|
|
|
unless topics.exists?(category_id: solved_category_id)
|
|
topics.last.update(category_id: solved_category_id)
|
|
end
|
|
|
|
topics = topics.where(category_id: solved_category_id)
|
|
end
|
|
|
|
solved_topic = DiscourseDev::Record.random(topics)
|
|
post = nil
|
|
|
|
if solved_topic.posts_count > 1
|
|
post = DiscourseDev::Record.random(solved_topic.posts.where.not(post_number: 1))
|
|
else
|
|
post = DiscourseDev::Post.new(solved_topic, 1).create!
|
|
end
|
|
|
|
DiscourseSolved.accept_answer!(post, post.topic.user, topic: post.topic)
|
|
end
|
|
end
|
|
|
|
query =
|
|
"
|
|
WITH x AS (SELECT
|
|
u.id user_id,
|
|
COUNT(DISTINCT ua.id) AS solutions
|
|
FROM users AS u
|
|
LEFT OUTER JOIN user_actions AS ua ON ua.user_id = u.id AND ua.action_type = #{UserAction::SOLVED} AND COALESCE(ua.created_at, :since) > :since
|
|
WHERE u.active
|
|
AND u.silenced_till IS NULL
|
|
AND u.id > 0
|
|
GROUP BY u.id
|
|
)
|
|
UPDATE directory_items di SET
|
|
solutions = x.solutions
|
|
FROM x
|
|
WHERE x.user_id = di.user_id
|
|
AND di.period_type = :period_type
|
|
AND di.solutions <> x.solutions
|
|
"
|
|
add_directory_column("solutions", query: query) if respond_to?(:add_directory_column)
|
|
|
|
add_to_class(:composer_messages_finder, :check_topic_is_solved) do
|
|
return if !SiteSetting.solved_enabled || SiteSetting.disable_solved_education_message
|
|
return if !replying? || @topic.blank? || @topic.private_message?
|
|
return if @topic.custom_fields[::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD].blank?
|
|
|
|
{
|
|
id: "solved_topic",
|
|
templateName: "education",
|
|
wait_for_typing: true,
|
|
extraClass: "education-message",
|
|
hide_if_whisper: true,
|
|
body: PrettyText.cook(I18n.t("education.topic_is_solved", base_url: Discourse.base_url)),
|
|
}
|
|
end
|
|
|
|
if defined?(UserAction::SOLVED)
|
|
add_to_serializer(:user_card, :accepted_answers) do
|
|
UserAction.where(user_id: object.id).where(action_type: UserAction::SOLVED).count
|
|
end
|
|
end
|
|
|
|
if respond_to?(:register_topic_list_preload_user_ids)
|
|
register_topic_list_preload_user_ids do |topics, user_ids, topic_list|
|
|
answer_post_ids =
|
|
TopicCustomField
|
|
.select("value::INTEGER")
|
|
.where(name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD)
|
|
.where(topic_id: topics.map(&:id))
|
|
answer_user_ids = Post.where(id: answer_post_ids).pluck(:topic_id, :user_id).to_h
|
|
topics.each { |topic| topic.accepted_answer_user_id = answer_user_ids[topic.id] }
|
|
user_ids.concat(answer_user_ids.values)
|
|
end
|
|
end
|
|
|
|
if defined?(DiscourseAutomation)
|
|
if respond_to?(:add_triggerable_to_scriptable)
|
|
on(:accepted_solution) do |post|
|
|
# testing directly automation is prone to issues
|
|
# we prefer to abstract logic in service object and test this
|
|
next if Rails.env.test?
|
|
|
|
name = "first_accepted_solution"
|
|
DiscourseAutomation::Automation
|
|
.where(trigger: name, enabled: true)
|
|
.find_each do |automation|
|
|
maximum_trust_level = automation.trigger_field("maximum_trust_level")&.dig("value")
|
|
if FirstAcceptedPostSolutionValidator.check(post, trust_level: maximum_trust_level)
|
|
automation.trigger!(
|
|
"kind" => name,
|
|
"accepted_post_id" => post.id,
|
|
"usernames" => [post.user.username],
|
|
"placeholders" => {
|
|
"post_url" => Discourse.base_url + post.url,
|
|
},
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
add_triggerable_to_scriptable(:first_accepted_solution, :send_pms)
|
|
|
|
DiscourseAutomation::Triggerable.add(:first_accepted_solution) do
|
|
placeholder :post_url
|
|
|
|
field :maximum_trust_level,
|
|
component: :choices,
|
|
extra: {
|
|
content: [
|
|
{
|
|
id: 1,
|
|
name:
|
|
"discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl1",
|
|
},
|
|
{
|
|
id: 2,
|
|
name:
|
|
"discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl2",
|
|
},
|
|
{
|
|
id: 3,
|
|
name:
|
|
"discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl3",
|
|
},
|
|
{
|
|
id: 4,
|
|
name:
|
|
"discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl4",
|
|
},
|
|
{
|
|
id: "any",
|
|
name:
|
|
"discourse_automation.triggerables.first_accepted_solution.max_trust_level.any",
|
|
},
|
|
],
|
|
},
|
|
required: true
|
|
end
|
|
end
|
|
end
|
|
end
|