Skip to content

DEV: Autoload and segregate features to prep for migration #341

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions app/lib/first_accepted_post_solution_validator.rb

This file was deleted.

40 changes: 0 additions & 40 deletions app/lib/plugin_initializers/assigned_reminder_exclude_solved.rb

This file was deleted.

27 changes: 27 additions & 0 deletions app/serializers/concerns/discourse_solved/topic_answer_mixin.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module DiscourseSolved
module TopicAnswerMixin
def self.included(klass)
klass.attributes :has_accepted_answer, :can_have_answer
end

def has_accepted_answer
object.custom_fields[::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD].present?
end

def include_has_accepted_answer?
SiteSetting.solved_enabled
end

def can_have_answer
return true if SiteSetting.allow_solved_on_all_topics
return false if object.closed || object.archived
scope.allow_accepted_answers?(object.category_id, object.tags.map(&:name))
end

def include_can_have_answer?
SiteSetting.solved_enabled && SiteSetting.empty_box_on_unsolved
end
end
end
25 changes: 0 additions & 25 deletions app/serializers/concerns/topic_answer_mixin.rb

This file was deleted.

8 changes: 8 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

DiscourseSolved::Engine.routes.draw do
post "/accept" => "answer#accept"
post "/unaccept" => "answer#unaccept"
end

Discourse::Application.routes.draw { mount ::DiscourseSolved::Engine, at: "solution" }
47 changes: 47 additions & 0 deletions lib/discourse_assign/entry_point.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

module DiscourseAssign
class EntryPoint
def self.inject(plugin)
plugin.register_modifier(:assigns_reminder_assigned_topics_query) do |query|
next query if !SiteSetting.ignore_solved_topics_in_assigned_reminder
query.where.not(
id:
TopicCustomField.where(
name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD,
).pluck(:topic_id),
)
end

plugin.register_modifier(:assigned_count_for_user_query) do |query, user|
next query if !SiteSetting.ignore_solved_topics_in_assigned_reminder
next query if SiteSetting.assignment_status_on_solve.blank?
query.where.not(status: SiteSetting.assignment_status_on_solve)
end

plugin.on(:accepted_solution) do |post|
next if SiteSetting.assignment_status_on_solve.blank?
assignments = Assignment.includes(:target).where(topic: post.topic)
assignments.each do |assignment|
assigned_user = User.find_by(id: assignment.assigned_to_id)
Assigner.new(assignment.target, assigned_user).assign(
assigned_user,
status: SiteSetting.assignment_status_on_solve,
)
end
end

plugin.on(:unaccepted_solution) do |post|
next if SiteSetting.assignment_status_on_unsolve.blank?
assignments = Assignment.includes(:target).where(topic: post.topic)
assignments.each do |assignment|
assigned_user = User.find_by(id: assignment.assigned_to_id)
Assigner.new(assignment.target, assigned_user).assign(
assigned_user,
status: SiteSetting.assignment_status_on_unsolve,
)
end
end
end
end
end
69 changes: 69 additions & 0 deletions lib/discourse_automation/entry_point.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

module DiscourseAutomation
class EntryPoint
def self.inject(plugin)
plugin.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

plugin.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
57 changes: 57 additions & 0 deletions lib/discourse_dev/discourse_solved.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

module DiscourseDev
class DiscourseSolved
def self.populate(plugin)
plugin.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
end
end
end
File renamed without changes.
9 changes: 9 additions & 0 deletions lib/discourse_solved/engine.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module ::DiscourseSolved
class Engine < ::Rails::Engine
engine_name PLUGIN_NAME
isolate_namespace DiscourseSolved
config.autoload_paths << File.join(config.root, "lib")
end
end
19 changes: 19 additions & 0 deletions lib/discourse_solved/first_accepted_post_solution_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module DiscourseSolved
class FirstAcceptedPostSolutionValidator
def self.check(post, trust_level:)
return false if post.archetype != Archetype.default
return false if !post&.user&.human?
return true if trust_level == "any"

return false if TrustLevel.compare(post&.user&.trust_level, trust_level.to_i)

if !UserAction.where(user_id: post&.user_id, action_type: UserAction::SOLVED).exists?
return true
end

false
end
end
end
69 changes: 69 additions & 0 deletions lib/discourse_solved/register_filters.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

module DiscourseSolved
class RegisterFilters
def self.register(plugin)
solved_callback = ->(scope) do
sql = <<~SQL
topics.id IN (
SELECT topic_id
FROM topic_custom_fields
WHERE name = '#{::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD}'
AND value IS NOT NULL
)
SQL

scope.where(sql).where("topics.archetype <> ?", Archetype.private_message)
end
unsolved_callback = ->(scope) do
scope = scope.where <<~SQL
topics.id NOT IN (
SELECT topic_id
FROM topic_custom_fields
WHERE name = '#{::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD}'
AND value IS NOT NULL
)
SQL

if !SiteSetting.allow_solved_on_all_topics
tag_ids = Tag.where(name: SiteSetting.enable_solved_tags.split("|")).pluck(:id)

scope = scope.where <<~SQL, tag_ids
topics.id IN (
SELECT t.id
FROM topics t
JOIN category_custom_fields cc
ON t.category_id = cc.category_id
AND cc.name = '#{::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD}'
AND cc.value = 'true'
)
OR
topics.id IN (
SELECT topic_id
FROM topic_tags
WHERE tag_id IN (?)
)
SQL
end

scope.where("topics.archetype <> ?", Archetype.private_message)
end

plugin.register_custom_filter_by_status("solved", &solved_callback)
plugin.register_custom_filter_by_status("unsolved", &unsolved_callback)

plugin.register_search_advanced_filter(/status:solved/, &solved_callback)
plugin.register_search_advanced_filter(/status:unsolved/, &unsolved_callback)

TopicQuery.add_custom_filter(:solved) do |results, topic_query|
if topic_query.options[:solved] == "yes"
solved_callback.call(results)
elsif topic_query.options[:solved] == "no"
unsolved_callback.call(results)
else
results
end
end
end
end
end
Loading