Skip to content

Commit e82c6ae

Browse files
authored
DEV: Autoload and segregate features to prep for migration (#341)
This commit autoloads plugin files, and also extracts features into their own modules. - `plugin.rb` is smaller - external plugins like discourse-automation and discourse-assign have their own entrypoints - solved filters as well
1 parent 4e3521f commit e82c6ae

24 files changed

+358
-355
lines changed

app/lib/first_accepted_post_solution_validator.rb

Lines changed: 0 additions & 17 deletions
This file was deleted.

app/lib/plugin_initializers/assigned_reminder_exclude_solved.rb

Lines changed: 0 additions & 40 deletions
This file was deleted.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseSolved
4+
module TopicAnswerMixin
5+
def self.included(klass)
6+
klass.attributes :has_accepted_answer, :can_have_answer
7+
end
8+
9+
def has_accepted_answer
10+
object.custom_fields[::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD].present?
11+
end
12+
13+
def include_has_accepted_answer?
14+
SiteSetting.solved_enabled
15+
end
16+
17+
def can_have_answer
18+
return true if SiteSetting.allow_solved_on_all_topics
19+
return false if object.closed || object.archived
20+
scope.allow_accepted_answers?(object.category_id, object.tags.map(&:name))
21+
end
22+
23+
def include_can_have_answer?
24+
SiteSetting.solved_enabled && SiteSetting.empty_box_on_unsolved
25+
end
26+
end
27+
end

app/serializers/concerns/topic_answer_mixin.rb

Lines changed: 0 additions & 25 deletions
This file was deleted.

config/routes.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# frozen_string_literal: true
2+
3+
DiscourseSolved::Engine.routes.draw do
4+
post "/accept" => "answer#accept"
5+
post "/unaccept" => "answer#unaccept"
6+
end
7+
8+
Discourse::Application.routes.draw { mount ::DiscourseSolved::Engine, at: "solution" }

lib/discourse_assign/entry_point.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAssign
4+
class EntryPoint
5+
def self.inject(plugin)
6+
plugin.register_modifier(:assigns_reminder_assigned_topics_query) do |query|
7+
next query if !SiteSetting.ignore_solved_topics_in_assigned_reminder
8+
query.where.not(
9+
id:
10+
TopicCustomField.where(
11+
name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD,
12+
).pluck(:topic_id),
13+
)
14+
end
15+
16+
plugin.register_modifier(:assigned_count_for_user_query) do |query, user|
17+
next query if !SiteSetting.ignore_solved_topics_in_assigned_reminder
18+
next query if SiteSetting.assignment_status_on_solve.blank?
19+
query.where.not(status: SiteSetting.assignment_status_on_solve)
20+
end
21+
22+
plugin.on(:accepted_solution) do |post|
23+
next if SiteSetting.assignment_status_on_solve.blank?
24+
assignments = Assignment.includes(:target).where(topic: post.topic)
25+
assignments.each do |assignment|
26+
assigned_user = User.find_by(id: assignment.assigned_to_id)
27+
Assigner.new(assignment.target, assigned_user).assign(
28+
assigned_user,
29+
status: SiteSetting.assignment_status_on_solve,
30+
)
31+
end
32+
end
33+
34+
plugin.on(:unaccepted_solution) do |post|
35+
next if SiteSetting.assignment_status_on_unsolve.blank?
36+
assignments = Assignment.includes(:target).where(topic: post.topic)
37+
assignments.each do |assignment|
38+
assigned_user = User.find_by(id: assignment.assigned_to_id)
39+
Assigner.new(assignment.target, assigned_user).assign(
40+
assigned_user,
41+
status: SiteSetting.assignment_status_on_unsolve,
42+
)
43+
end
44+
end
45+
end
46+
end
47+
end
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAutomation
4+
class EntryPoint
5+
def self.inject(plugin)
6+
plugin.on(:accepted_solution) do |post|
7+
# testing directly automation is prone to issues
8+
# we prefer to abstract logic in service object and test this
9+
next if Rails.env.test?
10+
11+
name = "first_accepted_solution"
12+
DiscourseAutomation::Automation
13+
.where(trigger: name, enabled: true)
14+
.find_each do |automation|
15+
maximum_trust_level = automation.trigger_field("maximum_trust_level")&.dig("value")
16+
if FirstAcceptedPostSolutionValidator.check(post, trust_level: maximum_trust_level)
17+
automation.trigger!(
18+
"kind" => name,
19+
"accepted_post_id" => post.id,
20+
"usernames" => [post.user.username],
21+
"placeholders" => {
22+
"post_url" => Discourse.base_url + post.url,
23+
},
24+
)
25+
end
26+
end
27+
end
28+
29+
plugin.add_triggerable_to_scriptable(:first_accepted_solution, :send_pms)
30+
31+
DiscourseAutomation::Triggerable.add(:first_accepted_solution) do
32+
placeholder :post_url
33+
34+
field :maximum_trust_level,
35+
component: :choices,
36+
extra: {
37+
content: [
38+
{
39+
id: 1,
40+
name:
41+
"discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl1",
42+
},
43+
{
44+
id: 2,
45+
name:
46+
"discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl2",
47+
},
48+
{
49+
id: 3,
50+
name:
51+
"discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl3",
52+
},
53+
{
54+
id: 4,
55+
name:
56+
"discourse_automation.triggerables.first_accepted_solution.max_trust_level.tl4",
57+
},
58+
{
59+
id: "any",
60+
name:
61+
"discourse_automation.triggerables.first_accepted_solution.max_trust_level.any",
62+
},
63+
],
64+
},
65+
required: true
66+
end
67+
end
68+
end
69+
end

lib/discourse_dev/discourse_solved.rb

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseDev
4+
class DiscourseSolved
5+
def self.populate(plugin)
6+
plugin.on(:after_populate_dev_records) do |records, type|
7+
next unless SiteSetting.solved_enabled
8+
9+
if type == :category
10+
next if SiteSetting.allow_solved_on_all_topics
11+
12+
solved_category =
13+
DiscourseDev::Record.random(
14+
Category.where(
15+
read_restricted: false,
16+
id: records.pluck(:id),
17+
parent_category_id: nil,
18+
),
19+
)
20+
CategoryCustomField.create!(
21+
category_id: solved_category.id,
22+
name: ::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD,
23+
value: "true",
24+
)
25+
puts "discourse-solved enabled on category '#{solved_category.name}' (#{solved_category.id})."
26+
elsif type == :topic
27+
topics = Topic.where(id: records.pluck(:id))
28+
29+
unless SiteSetting.allow_solved_on_all_topics
30+
solved_category_id =
31+
CategoryCustomField
32+
.where(name: ::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD, value: "true")
33+
.first
34+
.category_id
35+
36+
unless topics.exists?(category_id: solved_category_id)
37+
topics.last.update(category_id: solved_category_id)
38+
end
39+
40+
topics = topics.where(category_id: solved_category_id)
41+
end
42+
43+
solved_topic = DiscourseDev::Record.random(topics)
44+
post = nil
45+
46+
if solved_topic.posts_count > 1
47+
post = DiscourseDev::Record.random(solved_topic.posts.where.not(post_number: 1))
48+
else
49+
post = DiscourseDev::Post.new(solved_topic, 1).create!
50+
end
51+
52+
::DiscourseSolved.accept_answer!(post, post.topic.user, topic: post.topic)
53+
end
54+
end
55+
end
56+
end
57+
end

lib/discourse_solved/engine.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
module ::DiscourseSolved
4+
class Engine < ::Rails::Engine
5+
engine_name PLUGIN_NAME
6+
isolate_namespace DiscourseSolved
7+
config.autoload_paths << File.join(config.root, "lib")
8+
end
9+
end
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseSolved
4+
class FirstAcceptedPostSolutionValidator
5+
def self.check(post, trust_level:)
6+
return false if post.archetype != Archetype.default
7+
return false if !post&.user&.human?
8+
return true if trust_level == "any"
9+
10+
return false if TrustLevel.compare(post&.user&.trust_level, trust_level.to_i)
11+
12+
if !UserAction.where(user_id: post&.user_id, action_type: UserAction::SOLVED).exists?
13+
return true
14+
end
15+
16+
false
17+
end
18+
end
19+
end
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseSolved
4+
class RegisterFilters
5+
def self.register(plugin)
6+
solved_callback = ->(scope) do
7+
sql = <<~SQL
8+
topics.id IN (
9+
SELECT topic_id
10+
FROM topic_custom_fields
11+
WHERE name = '#{::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD}'
12+
AND value IS NOT NULL
13+
)
14+
SQL
15+
16+
scope.where(sql).where("topics.archetype <> ?", Archetype.private_message)
17+
end
18+
unsolved_callback = ->(scope) do
19+
scope = scope.where <<~SQL
20+
topics.id NOT IN (
21+
SELECT topic_id
22+
FROM topic_custom_fields
23+
WHERE name = '#{::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD}'
24+
AND value IS NOT NULL
25+
)
26+
SQL
27+
28+
if !SiteSetting.allow_solved_on_all_topics
29+
tag_ids = Tag.where(name: SiteSetting.enable_solved_tags.split("|")).pluck(:id)
30+
31+
scope = scope.where <<~SQL, tag_ids
32+
topics.id IN (
33+
SELECT t.id
34+
FROM topics t
35+
JOIN category_custom_fields cc
36+
ON t.category_id = cc.category_id
37+
AND cc.name = '#{::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD}'
38+
AND cc.value = 'true'
39+
)
40+
OR
41+
topics.id IN (
42+
SELECT topic_id
43+
FROM topic_tags
44+
WHERE tag_id IN (?)
45+
)
46+
SQL
47+
end
48+
49+
scope.where("topics.archetype <> ?", Archetype.private_message)
50+
end
51+
52+
plugin.register_custom_filter_by_status("solved", &solved_callback)
53+
plugin.register_custom_filter_by_status("unsolved", &unsolved_callback)
54+
55+
plugin.register_search_advanced_filter(/status:solved/, &solved_callback)
56+
plugin.register_search_advanced_filter(/status:unsolved/, &unsolved_callback)
57+
58+
TopicQuery.add_custom_filter(:solved) do |results, topic_query|
59+
if topic_query.options[:solved] == "yes"
60+
solved_callback.call(results)
61+
elsif topic_query.options[:solved] == "no"
62+
unsolved_callback.call(results)
63+
else
64+
results
65+
end
66+
end
67+
end
68+
end
69+
end

0 commit comments

Comments
 (0)