Files
discourse/plugins/chat/app/services/chat/search_chatable.rb
Régis Hanol 4b49e358fb FIX: skip 1:1s when chat search returns users (#28464)
When we use CTRL/CMD + K to search, on the server we run 4 queries:

- 1 for users
- 1 for groups
- 1 for category channels
- 1 for direct message channels

The server returns up to 10 results per type and the client concatenate all the results (in the order I described) and then takes the first 10 or so.

Let's say you've had 1:1s with john1, john2, and john3. Searching for “john” would return all the johns as “users”. It doesn’t make sense to also return them as 1:1 dms.

That’s what this fixes. When the search returns some users, we skip all 1:1s because we assume they will show up as the results of the “users” query.

We’ll always return matching direct messages with more than 2 users (aka. “group chats”). All 3 other queries (users, groups, and category channels) are unaffected.

Internal ref - t/136079
2024-08-22 11:35:13 +02:00

141 lines
4.3 KiB
Ruby

# frozen_string_literal: true
module Chat
# Returns a list of chatables (users, groups ,category channels, direct message channels) that can be chatted with.
#
# @example
# Chat::SearchChatable.call(term: "@bob", guardian: guardian)
#
class SearchChatable
include Service::Base
SEARCH_RESULT_LIMIT ||= 10
# @!method call(term:, guardian:)
# @param [String] term
# @param [Guardian] guardian
# @return [Service::Base::Context]
contract
step :clean_term
model :memberships, optional: true
model :users, optional: true
model :groups, optional: true
model :category_channels, optional: true
model :direct_message_channels, optional: true
# @!visibility private
class Contract
attribute :term, :string, default: ""
attribute :include_users, :boolean, default: true
attribute :include_groups, :boolean, default: true
attribute :include_category_channels, :boolean, default: true
attribute :include_direct_message_channels, :boolean, default: true
attribute :excluded_memberships_channel_id, :integer
end
private
def clean_term(contract:)
context.term = contract.term&.downcase&.strip&.gsub(/^[@#]+/, "")
end
def fetch_memberships(guardian:)
::Chat::ChannelMembershipManager.all_for_user(guardian.user)
end
def fetch_users(guardian:, contract:)
return unless contract.include_users
return unless guardian.can_create_direct_message?
search_users(context, guardian)
end
def fetch_groups(guardian:, contract:)
return unless contract.include_groups
return unless guardian.can_create_direct_message?
search_groups(context, guardian)
end
def fetch_category_channels(guardian:, contract:)
return unless contract.include_category_channels
return unless SiteSetting.enable_public_channels
search_category_channels(context, guardian)
end
def fetch_direct_message_channels(guardian:, contract:, users:, **args)
return unless contract.include_direct_message_channels
return unless guardian.can_create_direct_message?
search_direct_message_channels(context, guardian, contract, users)
end
def search_users(context, guardian)
user_search = ::UserSearch.new(context.term, limit: SEARCH_RESULT_LIMIT)
if context.term.blank?
user_search = user_search.scoped_users
else
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(
"NOT EXISTS (SELECT 1 FROM user_chat_channel_memberships WHERE user_id = users.id AND chat_channel_id = ?)",
context.excluded_memberships_channel_id,
)
end
user_search
end
def search_groups(context, guardian)
Group
.visible_groups(guardian.user)
.includes(users: :user_option)
.where(
"groups.name ILIKE :term_like OR groups.full_name ILIKE :term_like",
term_like: "%#{context.term}%",
)
.limit(SEARCH_RESULT_LIMIT)
end
def search_category_channels(context, guardian)
::Chat::ChannelFetcher.secured_public_channel_search(
guardian,
status: :open,
filter: context.term,
filter_on_category_name: false,
match_filter_on_starts_with: false,
limit: SEARCH_RESULT_LIMIT,
)
end
def search_direct_message_channels(context, guardian, contract, users)
channels =
::Chat::ChannelFetcher.secured_direct_message_channels_search(
guardian.user.id,
guardian,
filter: context.term,
match_filter_on_starts_with: false,
limit: SEARCH_RESULT_LIMIT,
) || []
# skip 1:1s when search returns users
if contract.include_users && users.present?
channels.reject! do |channel|
other_user_ids = channel.allowed_user_ids - [guardian.user.id]
other_user_ids.size <= 1
end
end
channels
end
end
end