FEATURE: allow access to assigns from forum researcher (#1412)

* FEATURE: allow access to assigns from forum researcher

* FIX: should properly be checking for empty

* finish PR
This commit is contained in:
Sam 2025-06-06 16:59:00 +10:00 committed by GitHub
parent 7d96739aab
commit 6817866de9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 90 additions and 2 deletions

View File

@ -134,7 +134,7 @@ module DiscourseAi
json_summary_schema_key = bot.persona.response_format&.first.to_h json_summary_schema_key = bot.persona.response_format&.first.to_h
helper_chunk = partial.read_buffered_property(json_summary_schema_key["key"]&.to_sym) helper_chunk = partial.read_buffered_property(json_summary_schema_key["key"]&.to_sym)
if helper_chunk.present? if !helper_chunk.nil? && !helper_chunk.empty?
helper_response << helper_chunk helper_response << helper_chunk
block.call(helper_chunk) if block block.call(helper_chunk) if block
end end

View File

@ -47,6 +47,7 @@ module DiscourseAi
- status:open|closed|archived|noreplies|single_user - topic status filters - status:open|closed|archived|noreplies|single_user - topic status filters
- max_results:N - limit results (per OR group) - max_results:N - limit results (per OR group)
- order:latest|oldest|latest_topic|oldest_topic|likes - sort order - order:latest|oldest|latest_topic|oldest_topic|likes - sort order
#{assign_tip}
**OR Logic:** Each OR group processes independently - filters don't cross boundaries. **OR Logic:** Each OR group processes independently - filters don't cross boundaries.
@ -56,6 +57,16 @@ module DiscourseAi
TEXT TEXT
end end
def assign_tip
if SiteSetting.respond_to?(:assign_enabled) && SiteSetting.assign_enabled
(<<~TEXT).strip
assigned_to:username or assigned_to:username1,username2 - topics assigned to a specific user
assigned_to:* - topics assigned to any user
assigned_to:nobody - topics not assigned to any user
TEXT
end
end
def name def name
"researcher" "researcher"
end end
@ -113,6 +124,8 @@ module DiscourseAi
else else
process_filter(filter, goals, post, &blk) process_filter(filter, goals, post, &blk)
end end
rescue StandardError => e
{ error: "Error processing research: #{e.message}" }
end end
def details def details

View File

@ -118,7 +118,7 @@ module DiscourseAi
partial_summary = partial_summary =
partial.read_buffered_property(json_summary_schema_key["key"]&.to_sym) partial.read_buffered_property(json_summary_schema_key["key"]&.to_sym)
if partial_summary.present? if !partial_summary.nil? && !partial_summary.empty?
summary << partial_summary summary << partial_summary
on_partial_blk.call(partial_summary) if on_partial_blk on_partial_blk.call(partial_summary) if on_partial_blk
end end

View File

@ -162,6 +162,34 @@ module DiscourseAi
end end
end end
def self.assign_allowed?(guardian)
SiteSetting.respond_to?(:assign_enabled) && SiteSetting.assign_enabled &&
(guardian.can_assign? || SiteSetting.assigns_public)
end
register_filter(/\Aassigned_to:(.+)\z/i) do |relation, name, filter|
if !assign_allowed?(filter.guardian)
raise Discourse::InvalidAccess.new(
"Assigns are not enabled or you do not have permission to see assigns.",
)
end
if (name == "nobody")
relation.joins("LEFT JOIN assignments a ON a.topic_id = topics.id AND a.active").where(
"a.assigned_to_id IS NULL",
)
elsif name == "*"
relation.joins("JOIN assignments a ON a.topic_id = topics.id AND a.active").where(
"a.assigned_to_id IS NOT NULL",
)
else
usernames = name.split(",").map(&:strip).map(&:downcase)
relation.joins("JOIN assignments a ON a.topic_id = topics.id AND a.active").where(
"a.assigned_to_id" => User.where(username_lower: usernames).select(:id),
)
end
end
register_filter(/\Agroups?:([a-zA-Z0-9_\-,]+)\z/i) do |relation, groups_param, filter| register_filter(/\Agroups?:([a-zA-Z0-9_\-,]+)\z/i) do |relation, groups_param, filter|
if groups_param.include?(",") if groups_param.include?(",")
group_names = groups_param.split(",").map(&:strip) group_names = groups_param.split(",").map(&:strip)

View File

@ -57,6 +57,8 @@ describe DiscourseAi::Utils::Research::Filter do
fab!(:bug_post) { Fabricate(:post, topic: bug_topic, user: user) } fab!(:bug_post) { Fabricate(:post, topic: bug_topic, user: user) }
fab!(:feature_bug_post) { Fabricate(:post, topic: feature_bug_topic, user: user) } fab!(:feature_bug_post) { Fabricate(:post, topic: feature_bug_topic, user: user) }
fab!(:no_tag_post) { Fabricate(:post, topic: no_tag_topic, user: user) } fab!(:no_tag_post) { Fabricate(:post, topic: no_tag_topic, user: user) }
fab!(:admin1) { Fabricate(:admin, username: "admin1") }
fab!(:admin2) { Fabricate(:admin, username: "admin2") }
describe "group filtering" do describe "group filtering" do
before do before do
@ -192,6 +194,51 @@ describe DiscourseAi::Utils::Research::Filter do
end end
end end
if SiteSetting.respond_to?(:assign_enabled)
describe "assign filtering" do
before do
SiteSetting.assign_enabled = true
assigner = Assigner.new(feature_topic, admin1)
assigner.assign(admin1)
assigner = Assigner.new(bug_topic, admin1)
assigner.assign(admin2)
end
let(:admin_guardian) { Guardian.new(admin1) }
it "can find topics assigned to a user" do
filter = described_class.new("assigned_to:#{admin1.username}", guardian: admin_guardian)
expect(filter.search.pluck(:id)).to contain_exactly(feature_post.id)
end
it "can find topics assigned to multiple users" do
filter =
described_class.new(
"assigned_to:#{admin1.username},#{admin2.username}",
guardian: admin_guardian,
)
expect(filter.search.pluck(:id)).to contain_exactly(feature_post.id, bug_post.id)
end
it "can find topics assigned to nobody" do
filter = described_class.new("assigned_to:nobody", guardian: admin_guardian)
expect(filter.search.pluck(:id)).to contain_exactly(feature_bug_post.id, no_tag_post.id)
end
it "can find all assigned topics" do
filter = described_class.new("assigned_to:*", guardian: admin_guardian)
expect(filter.search.pluck(:id)).to contain_exactly(feature_post.id, bug_post.id)
end
it "raises an error if assigns are disabled" do
SiteSetting.assign_enabled = false
filter = described_class.new("assigned_to:sam")
expect { filter.search }.to raise_error(Discourse::InvalidAccess)
end
end
end
it "can limit number of results" do it "can limit number of results" do
filter = described_class.new("category:Feedback max_results:1", limit: 5) filter = described_class.new("category:Feedback max_results:1", limit: 5)
expect(filter.search.pluck(:id).length).to eq(1) expect(filter.search.pluck(:id).length).to eq(1)