Skip to content

Commit 1b3fdad

Browse files
authored
FEATURE: allow researcher to also research specific topics (#1339)
* FEATURE: allow researcher to also research specific topics Also improve UI around research with more accurate info * this ensures that under no conditions PMs will be included
1 parent 2c64594 commit 1b3fdad

File tree

5 files changed

+115
-9
lines changed

5 files changed

+115
-9
lines changed

config/locales/server.en.yml

+5-4
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ en:
399399
create_image: "Creating image"
400400
edit_image: "Editing image"
401401
researcher: "Researching"
402+
researcher_dry_run: "Preparing research"
402403
tool_help:
403404
read_artifact: "Read a web artifact using the AI Bot"
404405
update_artifact: "Update a web artifact using the AI Bot"
@@ -461,11 +462,11 @@ en:
461462
setting_context: "Reading context for: %{setting_name}"
462463
schema: "%{tables}"
463464
researcher_dry_run:
464-
one: "Proposed research: %{goals}\n\nFound %{count} result for '%{filter}'"
465-
other: "Proposed research: %{goals}\n\nFound %{count} result for '%{filter}'"
465+
one: "Proposed goals: %{goals}\n\nFound %{count} post matching '%{filter}'"
466+
other: "Proposed goals: %{goals}\n\nFound %{count} posts matching '%{filter}'"
466467
researcher:
467-
one: "Researching: %{goals}\n\nFound %{count} result for '%{filter}'"
468-
other: "Researching: %{goals}\n\nFound %{count} result for '%{filter}'"
468+
one: "Researching: %{goals}\n\nFound %{count} post matching '%{filter}'"
469+
other: "Researching: %{goals}\n\nFound %{count} posts matching '%{filter}'"
469470
search_settings:
470471
one: "Found %{count} result for '%{query}'"
471472
other: "Found %{count} results for '%{query}'"

lib/personas/tools/researcher.rb

+11-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def signature
2222
},
2323
{
2424
name: "dry_run",
25-
description: "When true, only count matching items without processing data",
25+
description: "When true, only count matching posts without processing data",
2626
type: "boolean",
2727
},
2828
],
@@ -41,6 +41,7 @@ def filter_description
4141
- keywords (keywords:keyword1,keyword2) - specific words to search for in posts
4242
- max_results (max_results:10) the maximum number of results to return (optional)
4343
- order (order:latest, order:oldest, order:latest_topic, order:oldest_topic) - the order of the results (optional)
44+
- topic (topic:topic_id1,topic_id2) - add specific topics to the filter, topics will unconditionally be included
4445
4546
If multiple tags or categories are specified, they are treated as OR conditions.
4647
@@ -89,7 +90,7 @@ def invoke(&blk)
8990
blk.call details
9091

9192
if dry_run
92-
{ dry_run: true, goals: goals, filter: @filter, number_of_results: @result_count }
93+
{ dry_run: true, goals: goals, filter: @filter, number_of_posts: @result_count }
9394
else
9495
process_filter(filter, goals, post, &blk)
9596
end
@@ -103,6 +104,14 @@ def details
103104
end
104105
end
105106

107+
def summary
108+
if @dry_run
109+
I18n.t("discourse_ai.ai_bot.tool_summary.researcher_dry_run")
110+
else
111+
I18n.t("discourse_ai.ai_bot.tool_summary.researcher")
112+
end
113+
end
114+
106115
def description_args
107116
{ count: @result_count || 0, filter: @filter || "", goals: @goals || "" }
108117
end

lib/utils/research/filter.rb

+42-1
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,23 @@ def self.word_to_date(str)
188188
relation
189189
end
190190

191+
register_filter(/\Atopics?:(.*)\z/i) do |relation, topic_param, filter|
192+
if topic_param.include?(",")
193+
topic_ids = topic_param.split(",").map(&:strip).map(&:to_i).reject(&:zero?)
194+
return relation.where("1 = 0") if topic_ids.empty?
195+
filter.always_return_topic_ids!(topic_ids)
196+
relation
197+
else
198+
topic_id = topic_param.to_i
199+
if topic_id > 0
200+
filter.always_return_topic_ids!([topic_id])
201+
relation
202+
else
203+
relation.where("1 = 0") # No results if topic_id is invalid
204+
end
205+
end
206+
end
207+
191208
def initialize(term, guardian: nil, limit: nil, offset: nil)
192209
@term = term.to_s
193210
@guardian = guardian || Guardian.new
@@ -196,6 +213,7 @@ def initialize(term, guardian: nil, limit: nil, offset: nil)
196213
@filters = []
197214
@valid = true
198215
@order = :latest_post
216+
@topic_ids = nil
199217

200218
@term = process_filters(@term)
201219
end
@@ -204,17 +222,40 @@ def set_order!(order)
204222
@order = order
205223
end
206224

225+
def always_return_topic_ids!(topic_ids)
226+
if @topic_ids
227+
@topic_ids = @topic_ids + topic_ids
228+
else
229+
@topic_ids = topic_ids
230+
end
231+
end
232+
207233
def limit_by_user!(limit)
208234
@limit = limit if limit.to_i < @limit.to_i || @limit.nil?
209235
end
210236

211237
def search
212-
filtered = Post.secured(@guardian).joins(:topic).merge(Topic.secured(@guardian))
238+
filtered =
239+
Post
240+
.secured(@guardian)
241+
.joins(:topic)
242+
.merge(Topic.secured(@guardian))
243+
.where("topics.archetype = 'regular'")
244+
original_filtered = filtered
213245

214246
@filters.each do |filter_block, match_data|
215247
filtered = filter_block.call(filtered, match_data, self)
216248
end
217249

250+
if @topic_ids.present?
251+
filtered =
252+
original_filtered.where(
253+
"posts.topic_id IN (?) OR posts.id IN (?)",
254+
@topic_ids,
255+
filtered.select("posts.id"),
256+
)
257+
end
258+
218259
filtered = filtered.limit(@limit) if @limit.to_i > 0
219260
filtered = filtered.offset(@offset) if @offset.to_i > 0
220261

spec/lib/personas/tools/researcher_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
expect(results[:filter]).to eq("tag:research after:2023")
3636
expect(results[:goals]).to eq("analyze post patterns")
3737
expect(results[:dry_run]).to eq(true)
38-
expect(results[:number_of_results]).to be > 0
38+
expect(results[:number_of_posts]).to be > 0
3939
expect(researcher.filter).to eq("tag:research after:2023")
4040
expect(researcher.result_count).to be > 0
4141
end

spec/lib/utils/research/filter_spec.rb

+56-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
describe DiscourseAi::Utils::Research::Filter do
44
describe "integration tests" do
5-
before_all { SiteSetting.min_topic_title_length = 3 }
5+
before_all do
6+
SiteSetting.min_topic_title_length = 3
7+
SiteSetting.min_personal_message_title_length = 3
8+
end
69

710
fab!(:user)
811

@@ -51,6 +54,46 @@
5154
fab!(:feature_bug_post) { Fabricate(:post, topic: feature_bug_topic, user: user) }
5255
fab!(:no_tag_post) { Fabricate(:post, topic: no_tag_topic, user: user) }
5356

57+
describe "security filtering" do
58+
fab!(:secure_group) { Fabricate(:group) }
59+
fab!(:secure_category) { Fabricate(:category, name: "Secure") }
60+
61+
fab!(:secure_topic) do
62+
secure_category.set_permissions(secure_group => :readonly)
63+
secure_category.save!
64+
Fabricate(
65+
:topic,
66+
category: secure_category,
67+
user: user,
68+
title: "This is a secret Secret Topic",
69+
)
70+
end
71+
72+
fab!(:secure_post) { Fabricate(:post, topic: secure_topic, user: user) }
73+
74+
fab!(:pm_topic) { Fabricate(:private_message_topic, user: user) }
75+
fab!(:pm_post) { Fabricate(:post, topic: pm_topic, user: user) }
76+
77+
it "omits secure categories when no guardian is supplied" do
78+
filter = described_class.new("")
79+
expect(filter.search.pluck(:id)).not_to include(secure_post.id)
80+
81+
user.groups << secure_group
82+
guardian = Guardian.new(user)
83+
filter_with_guardian = described_class.new("", guardian: guardian)
84+
expect(filter_with_guardian.search.pluck(:id)).to include(secure_post.id)
85+
end
86+
87+
it "omits PMs unconditionally" do
88+
filter = described_class.new("")
89+
expect(filter.search.pluck(:id)).not_to include(pm_post.id)
90+
91+
guardian = Guardian.new(user)
92+
filter_with_guardian = described_class.new("", guardian: guardian)
93+
expect(filter_with_guardian.search.pluck(:id)).not_to include(pm_post.id)
94+
end
95+
end
96+
5497
describe "tag filtering" do
5598
it "correctly filters posts by tags" do
5699
filter = described_class.new("tag:feature")
@@ -76,6 +119,18 @@
76119
filter = described_class.new("category:Announcements")
77120
expect(filter.search.pluck(:id)).to contain_exactly(feature_post.id, bug_post.id)
78121

122+
# it can tack on topics
123+
filter =
124+
described_class.new(
125+
"category:Announcements topic:#{feature_bug_post.topic.id},#{no_tag_post.topic.id}",
126+
)
127+
expect(filter.search.pluck(:id)).to contain_exactly(
128+
feature_post.id,
129+
bug_post.id,
130+
feature_bug_post.id,
131+
no_tag_post.id,
132+
)
133+
79134
filter = described_class.new("category:Announcements,Feedback")
80135
expect(filter.search.pluck(:id)).to contain_exactly(
81136
feature_post.id,

0 commit comments

Comments
 (0)