Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit bbeff5a

Browse files
committed
improve filter implementation
1 parent 5225456 commit bbeff5a

File tree

3 files changed

+130
-23
lines changed

3 files changed

+130
-23
lines changed

lib/personas/tools/researcher.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ def filter_description
3434
Filter string to target specific content.
3535
- Supports user (@username)
3636
- date ranges (after:YYYY-MM-DD, before:YYYY-MM-DD for posts; topic_after:YYYY-MM-DD, topic_before:YYYY-MM-DD for topics)
37-
- categories (category:name)
38-
- tags (tag:name)
39-
- groups (group:name).
37+
- categories (category:category1,category2)
38+
- tags (tag:tag1,tag2)
39+
- groups (group:group1,group2).
40+
- status (status:open, status:closed, status:archived, status:noreplies, status:single_user)
41+
42+
If multiple tags or categories are specified, they are treated as OR conditions.
4043
4144
Multiple filters can be combined with spaces. Example: '@sam after:2023-01-01 tag:feature'
4245
TEXT

lib/utils/research/filter.rb

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -73,33 +73,43 @@ def self.word_to_date(str)
7373
end
7474
end
7575

76-
# Category filter
77-
register_filter(/\Acategory:([a-zA-Z0-9_\-]+)\z/i) do |relation, slug, _|
78-
category = Category.find_by("LOWER(slug) = LOWER(?)", slug)
79-
if category
80-
category_ids = [category.id]
81-
category_ids +=
82-
Category.subcategory_ids(category.id) if category.subcategory_ids.present?
83-
relation.where("topics.category_id IN (?)", category_ids)
76+
register_filter(/\A(?:tags?|tag):(.*)\z/i) do |relation, tag_param, _|
77+
if tag_param.include?(",")
78+
tag_names = tag_param.split(",").map(&:strip)
79+
tag_ids = Tag.where(name: tag_names).pluck(:id)
80+
return relation.where("1 = 0") if tag_ids.empty?
81+
relation.where(topic_id: TopicTag.where(tag_id: tag_ids).select(:topic_id))
8482
else
85-
relation.where("1 = 0") # No results if category doesn't exist
83+
if tag = Tag.find_by(name: tag_param)
84+
relation.where(topic_id: TopicTag.where(tag_id: tag.id).select(:topic_id))
85+
else
86+
relation.where("1 = 0")
87+
end
8688
end
8789
end
8890

89-
# Tag filter
90-
register_filter(/\Atag:([a-zA-Z0-9_\-]+)\z/i) do |relation, name, _|
91-
tag = Tag.find_by_name(name)
92-
if tag
93-
relation.joins("INNER JOIN topic_tags ON topic_tags.topic_id = topics.id").where(
94-
"topic_tags.tag_id = ?",
95-
tag.id,
96-
)
91+
register_filter(/\A(?:categories?|category):(.*)\z/i) do |relation, category_param, _|
92+
if category_param.include?(",")
93+
category_names = category_param.split(",").map(&:strip)
94+
95+
found_category_ids = []
96+
category_names.each do |name|
97+
category = Category.find_by(slug: name) || Category.find_by(name: name)
98+
found_category_ids << category.id if category
99+
end
100+
101+
return relation.where("1 = 0") if found_category_ids.empty?
102+
relation.where(topic_id: Topic.where(category_id: found_category_ids).select(:id))
97103
else
98-
relation.where("1 = 0") # No results if tag doesn't exist
104+
if category =
105+
Category.find_by(slug: category_param) || Category.find_by(name: category_param)
106+
relation.where(topic_id: Topic.where(category_id: category.id).select(:id))
107+
else
108+
relation.where("1 = 0")
109+
end
99110
end
100111
end
101112

102-
# User filter
103113
register_filter(/\A\@(\w+)\z/i) do |relation, username, filter|
104114
user = User.find_by(username_lower: username.downcase)
105115
if user
@@ -109,7 +119,6 @@ def self.word_to_date(str)
109119
end
110120
end
111121

112-
# Posted by current user
113122
register_filter(/\Ain:posted\z/i) do |relation, _, filter|
114123
if filter.guardian.user
115124
relation.where("posts.user_id = ?", filter.guardian.user.id)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# frozen_string_literal: true
2+
3+
describe DiscourseAi::Utils::Research::Filter do
4+
describe "integration tests" do
5+
before_all { SiteSetting.min_topic_title_length = 3 }
6+
7+
fab!(:user)
8+
9+
fab!(:feature_tag) { Fabricate(:tag, name: "feature") }
10+
fab!(:bug_tag) { Fabricate(:tag, name: "bug") }
11+
12+
fab!(:announcement_category) { Fabricate(:category, name: "Announcements") }
13+
fab!(:feedback_category) { Fabricate(:category, name: "Feedback") }
14+
15+
fab!(:feature_topic) do
16+
Fabricate(
17+
:topic,
18+
user: user,
19+
tags: [feature_tag],
20+
category: announcement_category,
21+
title: "New Feature Discussion",
22+
)
23+
end
24+
25+
fab!(:bug_topic) do
26+
Fabricate(
27+
:topic,
28+
tags: [bug_tag],
29+
user: user,
30+
category: announcement_category,
31+
title: "Bug Report",
32+
)
33+
end
34+
35+
fab!(:feature_bug_topic) do
36+
Fabricate(
37+
:topic,
38+
tags: [feature_tag, bug_tag],
39+
user: user,
40+
category: feedback_category,
41+
title: "Feature with Bug",
42+
)
43+
end
44+
45+
fab!(:no_tag_topic) do
46+
Fabricate(:topic, user: user, category: feedback_category, title: "General Discussion")
47+
end
48+
49+
fab!(:feature_post) { Fabricate(:post, topic: feature_topic, user: user) }
50+
fab!(:bug_post) { Fabricate(:post, topic: bug_topic, user: user) }
51+
fab!(:feature_bug_post) { Fabricate(:post, topic: feature_bug_topic, user: user) }
52+
fab!(:no_tag_post) { Fabricate(:post, topic: no_tag_topic, user: user) }
53+
54+
describe "tag filtering" do
55+
it "correctly filters posts by tags" do
56+
filter = described_class.new("tag:feature")
57+
expect(filter.search.pluck(:id)).to contain_exactly(feature_post.id, feature_bug_post.id)
58+
59+
filter = described_class.new("tag:feature,bug")
60+
expect(filter.search.pluck(:id)).to contain_exactly(
61+
feature_bug_post.id,
62+
bug_post.id,
63+
feature_post.id,
64+
)
65+
66+
filter = described_class.new("tags:bug")
67+
expect(filter.search.pluck(:id)).to contain_exactly(bug_post.id, feature_bug_post.id)
68+
69+
filter = described_class.new("tag:nonexistent")
70+
expect(filter.search.count).to eq(0)
71+
end
72+
end
73+
74+
describe "category filtering" do
75+
it "correctly filters posts by categories" do
76+
filter = described_class.new("category:Announcements")
77+
expect(filter.search.pluck(:id)).to contain_exactly(feature_post.id, bug_post.id)
78+
79+
filter = described_class.new("category:Announcements,Feedback")
80+
expect(filter.search.pluck(:id)).to contain_exactly(
81+
feature_post.id,
82+
bug_post.id,
83+
feature_bug_post.id,
84+
no_tag_post.id,
85+
)
86+
87+
filter = described_class.new("categories:Feedback")
88+
expect(filter.search.pluck(:id)).to contain_exactly(feature_bug_post.id, no_tag_post.id)
89+
90+
filter = described_class.new("category:Feedback tag:feature")
91+
expect(filter.search.pluck(:id)).to contain_exactly(feature_bug_post.id)
92+
end
93+
end
94+
end
95+
end

0 commit comments

Comments
 (0)