Skip to content

Commit

Permalink
FEATURE: add new custom status filters
Browse files Browse the repository at this point in the history
Those can be used in /filter route.

Depends on discourse/discourse#26770

Internal ref. /t/127278
  • Loading branch information
ZogStriP committed Apr 26, 2024
1 parent a18ce6d commit a49f23e
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 83 deletions.
165 changes: 82 additions & 83 deletions plugin.rb
Expand Up @@ -310,15 +310,16 @@ def self.skip_db?
end

if respond_to?(:register_modifier)
register_modifier(:search_rank_sort_priorities) do |priorities, search|
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}'
)
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

priorities.push([condition, 1.1])
Expand All @@ -344,82 +345,72 @@ def self.skip_db?
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
)",
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
)
end
SQL

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,
scope.where(sql)
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 (?)
)
end
SQL
end

scope
end

if Discourse.has_needed_version?(Discourse::VERSION::STRING, "1.8.0.beta6")
if respond_to? :register_custom_filter_by_status
register_custom_filter_by_status("solved", &solved_callback)
register_custom_filter_by_status("unsolved", &unsolved_callback)
end

if respond_to? :register_search_advanced_filter
register_search_advanced_filter(/status:solved/, &solved_callback)
register_search_advanced_filter(/status:unsolved/, &unsolved_callback)
end

if TopicQuery.respond_to? :add_custom_filter
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
)",
)
solved_callback.call(results)
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
)",
)
unsolved_callback.call(results)
else
results
end
results
end
end

Expand All @@ -432,19 +423,27 @@ def self.skip_db?
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)
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(:filter_auto_bump_topics) do |_category, filters|
filters.push(
->(r) do
sql = <<~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

r.where(sql)
end,
)
end

on(:before_post_publish_changes) do |post_changes, topic_changes, options|
category_id_changes = topic_changes.diff["category_id"].to_a
Expand Down
62 changes: 62 additions & 0 deletions spec/integration/solved_spec.rb
Expand Up @@ -9,6 +9,68 @@

before { SiteSetting.allow_solved_on_all_topics = true }

describe "customer filters" do
before do
SiteSetting.allow_solved_on_all_topics = false
SiteSetting.enable_solved_tags = solvable_tag.name
end

fab!(:solvable_category) do
category = Fabricate(:category)

CategoryCustomField.create(
category_id: category.id,
name: ::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD,
value: "true",
)

category
end

fab!(:solvable_tag) { Fabricate(:tag) }

fab!(:solved_in_category) do
Fabricate(
:custom_topic,
category: solvable_category,
custom_topic_name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD,
value: "42",
)
end

fab!(:solved_in_tag) do
Fabricate(
:custom_topic,
tags: [solvable_tag],
custom_topic_name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD,
value: "42",
)
end

fab!(:unsolved_in_category) { Fabricate(:topic, category: solvable_category) }
fab!(:unsolved_in_tag) { Fabricate(:topic, tags: [solvable_tag]) }

fab!(:unsolved_topic) { Fabricate(:topic) }

it "can filter by solved status" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("status:solved")
.pluck(:id),
).to contain_exactly(solved_in_category.id, solved_in_tag.id)
end

it "can filter by unsolved status" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("status:unsolved")
.pluck(:id),
).to contain_exactly(unsolved_in_category.id, unsolved_in_tag.id)
end
end

describe "search" do
before { SearchIndexer.enable }

Expand Down

0 comments on commit a49f23e

Please sign in to comment.