From e3a0faefc5a42cce94a24162c18836b6f4e1af79 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 27 Mar 2024 08:55:53 +1100 Subject: [PATCH] FEATURE: allow re-scoping chat user search via a plugin (#26361) This enables the following in Discourse AI ``` plugin.register_modifier(:chat_allowed_bot_user_ids) do |user_ids, guardian| if guardian.user mentionables = AiPersona.mentionables(user: guardian.user) allowed_bot_ids = mentionables.map { |mentionable| mentionable[:user_id] } user_ids.concat(allowed_bot_ids) end user_ids end ``` some bots that are id < 0 need to be discoverable in search otherwise people can not talk to them. --------- Co-authored-by: Joffrey JAFFEUX --- app/models/user.rb | 13 +++++++++--- .../chat/app/services/chat/search_chatable.rb | 10 +++++++-- .../services/chat/search_chatable_spec.rb | 21 +++++++++++++++++++ spec/models/user_spec.rb | 11 ++++++++++ 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 7283a46d608..2e58aacc053 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -259,12 +259,19 @@ class User < ActiveRecord::Base ) end - scope :human_users, -> { where("users.id > 0") } + scope :human_users, + ->(allowed_bot_user_ids: nil) do + if allowed_bot_user_ids.present? + where("users.id > 0 OR users.id IN (?)", allowed_bot_user_ids) + else + where("users.id > 0") + end + end # excluding fake users like the system user or anonymous users scope :real, - -> do - human_users.where( + ->(allowed_bot_user_ids: nil) do + human_users(allowed_bot_user_ids: allowed_bot_user_ids).where( "NOT EXISTS( SELECT 1 FROM anonymous_users a diff --git a/plugins/chat/app/services/chat/search_chatable.rb b/plugins/chat/app/services/chat/search_chatable.rb index b7c24d40901..e67a131890e 100644 --- a/plugins/chat/app/services/chat/search_chatable.rb +++ b/plugins/chat/app/services/chat/search_chatable.rb @@ -98,11 +98,17 @@ module Chat user_search = ::UserSearch.new(context.term, limit: 10) if context.term.blank? - user_search = user_search.scoped_users.real.includes(:user_option) + user_search = user_search.scoped_users else - user_search = user_search.search.real.includes(:user_option) + user_search = user_search.search end + allowed_bot_user_ids = + DiscoursePluginRegistry.apply_modifier(:chat_allowed_bot_user_ids, [], guardian) + + user_search = user_search.real(allowed_bot_user_ids: allowed_bot_user_ids) + user_search = user_search.includes(:user_option) + if context.excluded_memberships_channel_id user_search = user_search.where( diff --git a/plugins/chat/spec/services/chat/search_chatable_spec.rb b/plugins/chat/spec/services/chat/search_chatable_spec.rb index f6431c3bd2d..116d9d50fe5 100644 --- a/plugins/chat/spec/services/chat/search_chatable_spec.rb +++ b/plugins/chat/spec/services/chat/search_chatable_spec.rb @@ -82,6 +82,27 @@ RSpec.describe Chat::SearchChatable do expect(result.users).to_not include(current_user) end + + context "when chat_allowed_bot_user_ids modifier exists" do + fab!(:bot_1) { Fabricate(:user, id: -500) } + fab!(:bot_2) { Fabricate(:user, id: -501) } + + it "alters the users returned" do + modifier_block = Proc.new { [bot_2.id] } + plugin_instance = Plugin::Instance.new + plugin_instance.register_modifier(:chat_allowed_bot_user_ids, &modifier_block) + + expect(result.users).to_not include(bot_1) + expect(result.users).to include(bot_2) + expect(result.users).to include(current_user, sam, charlie, alain) + ensure + DiscoursePluginRegistry.unregister_modifier( + plugin_instance, + :chat_allowed_bot_user_ids, + &modifier_block + ) + end + end end context "when not including users" do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a6b569ea041..897a42df5ee 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1781,6 +1781,17 @@ RSpec.describe User do end end + describe "real users" do + it "should find system user if you allow it" do + ids = + User + .real(allowed_bot_user_ids: [Discourse.system_user.id]) + .where(id: Discourse.system_user.id) + .pluck(:id) + expect(ids).to eq([Discourse.system_user.id]) + end + end + describe "#purge_unactivated" do fab!(:user) { Fabricate(:user, refresh_auto_groups: true) } fab!(:unactivated) { Fabricate(:user, active: false) }