FEATURE: align with /filter and allow multiple category search (#27440)

This introduces the syntax of

`category:a,b,c` which will search across multiple categories.

Previously there was no way to allow search across a wide selection of
categories.
This commit is contained in:
Sam 2024-06-12 16:06:04 +10:00 committed by GitHub
parent be4f1e3350
commit dc8249c08a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 71 additions and 10 deletions

View File

@ -542,20 +542,46 @@ class Search
advanced_filter(/\Awith:images\z/i) { |posts| posts.where("posts.image_upload_id IS NOT NULL") }
advanced_filter(/\Acategory:(.+)\z/i) do |posts, match|
exact = false
advanced_filter(/\Acategor(?:y|ies):(.+)\z/i) do |posts, terms|
category_ids = []
if match[0] == "="
exact = true
match = match[1..-1]
matches =
terms
.split(",")
.map do |term|
if term[0] == "="
[term[1..-1], true]
else
[term, false]
end
end
.to_h
if matches.present?
sql = <<~SQL
SELECT c.id, term
FROM
categories c
JOIN
unnest(ARRAY[:matches]) AS term ON
c.slug ILIKE term OR
c.name ILIKE term OR
(term ~ '^[0-9]{1,10}$' AND c.id = term::int)
SQL
found = DB.query(sql, matches: matches.keys)
if found.present?
found.each do |row|
category_ids << row.id
@category_filter_matched ||= true
category_ids += Category.subcategory_ids(row.id) if !matches[row.term]
end
end
end
category_ids =
Category.where("slug ilike ? OR name ilike ? OR id = ?", match, match, match.to_i).pluck(:id)
if category_ids.present?
category_ids += Category.subcategory_ids(category_ids.first) unless exact
@category_filter_matched ||= true
posts.where("topics.category_id IN (?)", category_ids)
posts.where("topics.category_id IN (?)", category_ids.uniq)
else
posts.where("1 = 0")
end

View File

@ -1255,6 +1255,41 @@ RSpec.describe Search do
)
end
it "allow searching for multiple categories" do
category2 = Fabricate(:category, name: "abc")
topic2 = Fabricate(:topic, category: category2)
post2 = Fabricate(:post, topic: topic2, raw: "snow monkey")
category3 = Fabricate(:category, name: "def")
topic3 = Fabricate(:topic, category: category3)
post3 = Fabricate(:post, topic: topic3, raw: "snow monkey")
search = Search.execute("monkey category:abc,def")
expect(search.posts.map(&:id)).to contain_exactly(post2.id, post3.id)
search = Search.execute("monkey categories:abc,def")
expect(search.posts.map(&:id)).to contain_exactly(post2.id, post3.id)
search = Search.execute("monkey categories:xxxxx,=abc,=def")
expect(search.posts.map(&:id)).to contain_exactly(post2.id, post3.id)
search = Search.execute("snow category:abc,#{category.id}")
expect(search.posts.map(&:id)).to contain_exactly(post.id, post2.id)
child_category = Fabricate(:category, parent_category: category2)
child_topic = Fabricate(:topic, category: child_category)
child_post = Fabricate(:post, topic: child_topic, raw: "snow monkey")
search = Search.execute("monkey category:zzz,nnn,=abc,mmm")
expect(search.posts.map(&:id)).to contain_exactly(post2.id)
search =
Search.execute(
"monkey category:0007847874874874874748749398384398439843984938439843948394834984934839483984983498394834983498349834983,zzz,nnn,abc,mmm",
)
expect(search.posts.map(&:id)).to contain_exactly(post2.id, child_post.id)
end
it "should return the right categories" do
search = Search.execute("monkey")