FEATURE: Allow users to DM groups in chat (#25189)

Allows users to create DMs by selecting groups as a target. It also allows adding user groups to an existing chat

- When creating the channel, it expands the user group and adds all its members with chat enabled to the channel.
- After creation, there's no difference between adding a group or adding its members individually.
- Users can add multiple groups and users simultaneously.
- There are UI validations; the member count preview updates according to the member count of added groups, and it does not allow users to add more members than SiteSetting.chat_max_direct_message_users."
This commit is contained in:
Jan Cernik
2024-01-19 11:09:47 -03:00
committed by GitHub
parent bd2ca8d617
commit f4e51e0789
29 changed files with 573 additions and 91 deletions
@@ -20,6 +20,7 @@ module Chat
# @param [Integer] id of the channel
# @param [Hash] params_to_create
# @option params_to_create [Array<String>] usernames
# @option params_to_create [Array<String>] groups
# @return [Service::Base::Context]
contract
model :channel
@@ -27,6 +28,7 @@ module Chat
model :users, optional: true
transaction do
step :validate_user_count
step :upsert_memberships
step :recompute_users_count
step :notice_channel
@@ -35,20 +37,15 @@ module Chat
# @!visibility private
class Contract
attribute :usernames, :array
validates :usernames, presence: true
attribute :groups, :array
attribute :channel_id, :integer
validates :channel_id, presence: true
validate :usernames_length
validate :target_presence
def usernames_length
if usernames && usernames.length > SiteSetting.chat_max_direct_message_users + 1 # 1 for current user
errors.add(
:usernames,
"should have less than #{SiteSetting.chat_max_direct_message_users} elements",
)
end
def target_presence
usernames.present? || groups.present?
end
end
@@ -60,17 +57,23 @@ module Chat
end
def fetch_users(contract:, channel:, **)
::User.where(
"username IN (?) AND id NOT IN (?)",
[*contract.usernames],
channel.chatable.direct_message_users.select(:user_id),
).to_a
::Chat::UsersFromUsernamesAndGroupsQuery.call(
usernames: contract.usernames,
groups: contract.groups,
excluded_user_ids: channel.chatable.direct_message_users.pluck(:user_id),
)
end
def fetch_channel(contract:, **)
::Chat::Channel.includes(:chatable).find_by(id: contract.channel_id)
end
def validate_user_count(channel:, users:, **)
if channel.user_count + users.length > SiteSetting.chat_max_direct_message_users
fail!("should have less than #{SiteSetting.chat_max_direct_message_users} elements")
end
end
def upsert_memberships(channel:, users:, **)
always_level = ::Chat::UserChatChannelMembership::NOTIFICATION_LEVELS[:always]
@@ -19,6 +19,7 @@ module Chat
# @param [Guardian] guardian
# @param [Hash] params_to_create
# @option params_to_create [Array<String>] target_usernames
# @option params_to_create [Array<String>] target_groups
# @return [Service::Base::Context]
policy :can_create_direct_message
@@ -32,6 +33,7 @@ module Chat
class_name: Chat::DirectMessageChannel::CanCommunicateAllPartiesPolicy
model :direct_message, :fetch_or_create_direct_message
model :channel, :fetch_or_create_channel
step :validate_user_count
step :set_optional_name
step :update_memberships
step :recompute_users_count
@@ -40,7 +42,13 @@ module Chat
class Contract
attribute :name, :string
attribute :target_usernames, :array
validates :target_usernames, presence: true
attribute :target_groups, :array
validate :target_presence
def target_presence
target_usernames.present? || target_groups.present?
end
end
private
@@ -50,13 +58,22 @@ module Chat
end
def fetch_target_users(guardian:, contract:, **)
User.where(username: [guardian.user.username, *contract.target_usernames]).to_a
::Chat::UsersFromUsernamesAndGroupsQuery.call(
usernames: [*contract.target_usernames, guardian.user.username],
groups: contract.target_groups,
)
end
def fetch_user_comm_screener(target_users:, guardian:, **)
UserCommScreener.new(acting_user: guardian.user, target_user_ids: target_users.map(&:id))
end
def validate_user_count(target_users:, **)
if target_users.length > SiteSetting.chat_max_direct_message_users
fail!("should have less than #{SiteSetting.chat_max_direct_message_users} elements")
end
end
def actor_allows_dms(user_comm_screener:, **)
!user_comm_screener.actor_disallowing_all_pms?
end
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Chat
# Returns a list of chatables (users, category channels, direct message channels) that can be chatted with.
# 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)
@@ -18,6 +18,7 @@ module Chat
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
@@ -25,6 +26,7 @@ module Chat
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
@@ -46,6 +48,12 @@ module Chat
search_users(context, guardian, contract)
end
def fetch_groups(guardian:, contract:, **)
return unless contract.include_groups
return unless guardian.can_create_direct_message?
search_groups(context, guardian, contract)
end
def fetch_category_channels(guardian:, contract:, **)
return unless contract.include_category_channels
return if !SiteSetting.enable_public_channels
@@ -109,5 +117,15 @@ module Chat
user_search
end
def search_groups(context, guardian, contract)
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}%",
)
end
end
end