diff --git a/app/controllers/chat_controller.rb b/app/controllers/chat_controller.rb index 1e11e0a41..3f0d26f42 100644 --- a/app/controllers/chat_controller.rb +++ b/app/controllers/chat_controller.rb @@ -108,6 +108,12 @@ def create_message @user_chat_channel_membership.update( last_read_message_id: chat_message_creator.chat_message.id ) + + if @chat_channel.direct_message_channel? + @chat_channel.user_chat_channel_memberships.update_all(following: true) + ChatPublisher.publish_new_direct_message_channel(@chat_channel, @chat_channel.chatable.users) + end + ChatPublisher.publish_user_tracking_state( current_user, @chat_channel.id, diff --git a/app/controllers/direct_messages_controller.rb b/app/controllers/direct_messages_controller.rb index 960b25b58..da0fcd91f 100644 --- a/app/controllers/direct_messages_controller.rb +++ b/app/controllers/direct_messages_controller.rb @@ -7,16 +7,7 @@ def create users = [current_user] users.concat(User.where(username: params[:usernames].split(",")).to_a) if current_user.username != params[:usernames] - user_ids = users.map(&:id).uniq - - direct_messages_channel = DirectMessageChannel.for_user_ids(user_ids) - if direct_messages_channel - chat_channel = ChatChannel.find_by(chatable: direct_messages_channel) - else - chat_channel = DiscourseChat::DirectMessageChannelCreator.create(users) - ChatPublisher.publish_new_direct_message_channel(chat_channel, users) - end - + chat_channel = DiscourseChat::DirectMessageChannelCreator.create!(users) render_serialized(chat_channel, ChatChannelSerializer, root: "chat_channel") end end diff --git a/app/models/user_chat_channel_membership.rb b/app/models/user_chat_channel_membership.rb index 470ead2f4..b4253217b 100644 --- a/app/models/user_chat_channel_membership.rb +++ b/app/models/user_chat_channel_membership.rb @@ -27,7 +27,6 @@ class UserChatChannelMembership < ActiveRecord::Base def changes_for_direct_message_channels needs_validation = VALIDATED_ATTRS.any? { |attr| changed_attribute_names_to_save.include?(attr.to_s) } if needs_validation && chat_channel.direct_message_channel? - errors.add(:following) if !following errors.add(:muted) if muted errors.add(:desktop_notification_level) if desktop_notification_level.to_sym != :always errors.add(:mobile_notification_level) if mobile_notification_level.to_sym != :always diff --git a/assets/javascripts/discourse/components/chat-channel-row.js b/assets/javascripts/discourse/components/chat-channel-row.js index d189dc8fd..f7b04ffc5 100644 --- a/assets/javascripts/discourse/components/chat-channel-row.js +++ b/assets/javascripts/discourse/components/chat-channel-row.js @@ -1,14 +1,18 @@ +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; import Component from "@ember/component"; import discourseComputed from "discourse-common/utils/decorators"; import getURL from "discourse-common/lib/get-url"; import { equal } from "@ember/object/computed"; import { inject as service } from "@ember/service"; +import { action } from "@ember/object"; export default Component.extend({ channel: null, switchChannel: null, isDirectMessageRow: equal("channel.chatable_type", "DirectMessageChannel"), router: service(), + chat: service(), @discourseComputed("active", "channel.muted") rowClassNames(active, muted) { @@ -62,4 +66,15 @@ export default Component.extend({ hasUnread(trackingState) { return trackingState[this.channel.id]?.unread_count || 0; }, + + @action + leaveChatChannel() { + return ajax(`/chat/chat_channels/${this.channel.id}/unfollow`, { + method: "POST", + }) + .then(() => { + this.chat.stopTrackingChannel(this.channel); + }) + .catch(popupAjaxError); + }, }); diff --git a/assets/javascripts/discourse/templates/components/chat-channel-row.hbs b/assets/javascripts/discourse/templates/components/chat-channel-row.hbs index 18e347ecb..347a940b1 100644 --- a/assets/javascripts/discourse/templates/components/chat-channel-row.hbs +++ b/assets/javascripts/discourse/templates/components/chat-channel-row.hbs @@ -3,4 +3,13 @@ {{#if hasUnread}} {{/if}} + + {{#if isDirectMessageRow}} + {{d-button + icon="times" + action=(action "leaveChatChannel") + class="btn-flat leave-channel-btn" + title="chat.direct_messages.leave" + }} + {{/if}} diff --git a/assets/stylesheets/common/common.scss b/assets/stylesheets/common/common.scss index 235be8349..f559a98c4 100644 --- a/assets/stylesheets/common/common.scss +++ b/assets/stylesheets/common/common.scss @@ -1164,6 +1164,18 @@ body.composer-open .topic-chat-floar-container { cursor: pointer; color: var(--primary-high); + .leave-channel-btn { + margin-left: auto; + background: none; + visibility: hidden; + } + + &:hover { + .leave-channel-btn { + visibility: visible; + } + } + &:hover, &.active { background: var(--primary-low); diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 6babc5a73..fe25df729 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -132,6 +132,7 @@ en: title: "Personal chat" new: "New personal chat" create: "Go" + leave: "Leave this personal chat" incoming_webhooks: back: "Back" diff --git a/lib/direct_message_channel_creator.rb b/lib/direct_message_channel_creator.rb index 6243c5a7c..bf6b23f16 100644 --- a/lib/direct_message_channel_creator.rb +++ b/lib/direct_message_channel_creator.rb @@ -3,21 +3,35 @@ module DiscourseChat::DirectMessageChannelCreator attr_reader :chat_channel, :users - def self.create(users) - direct_messages_channel = DirectMessageChannel.create!(users: users) - chat_channel = ChatChannel.create!(chatable: direct_messages_channel) - users.each do |user| - UserChatChannelMembership.create( - user_id: user.id, - chat_channel_id: chat_channel.id, - last_read_message_id: nil, - following: true, - muted: false, - desktop_notification_level: UserChatChannelMembership::NOTIFICATION_LEVELS[:always], - mobile_notification_level: UserChatChannelMembership::NOTIFICATION_LEVELS[:always], - ) + def self.create!(users) + direct_messages_channel = DirectMessageChannel.for_user_ids(users.map(&:id).uniq) + if direct_messages_channel + chat_channel = ChatChannel.find_by!(chatable_id: direct_messages_channel.id) + else + direct_messages_channel = DirectMessageChannel.create!(users: users) + chat_channel = ChatChannel.create!(chatable: direct_messages_channel) end + update_memberships(users, chat_channel.id) + ChatPublisher.publish_new_direct_message_channel(chat_channel, users) chat_channel end + + private + + def self.update_memberships(users, chat_channel_id) + users.each do |user| + membership = UserChatChannelMembership.find_or_initialize_by(user_id: user.id, chat_channel_id: chat_channel_id) + + if membership.new_record? + membership.last_read_message_id = nil + membership.desktop_notification_level = UserChatChannelMembership::NOTIFICATION_LEVELS[:always] + membership.mobile_notification_level = UserChatChannelMembership::NOTIFICATION_LEVELS[:always] + membership.muted = false + end + + membership.following = true + membership.save! + end + end end diff --git a/spec/components/chat_message_creator_spec.rb b/spec/components/chat_message_creator_spec.rb index 4a4e5047e..ae58d1234 100644 --- a/spec/components/chat_message_creator_spec.rb +++ b/spec/components/chat_message_creator_spec.rb @@ -20,7 +20,7 @@ [admin1, admin2, user1, user2, user3].each do |user| Fabricate(:user_chat_channel_membership, chat_channel: public_chat_channel, user: user) end - @direct_message_channel = DiscourseChat::DirectMessageChannelCreator.create([user1, user2]) + @direct_message_channel = DiscourseChat::DirectMessageChannelCreator.create!([user1, user2]) end describe "Integration tests with jobs running immediately" do diff --git a/spec/components/chat_message_updater_spec.rb b/spec/components/chat_message_updater_spec.rb index 1ff076738..2243ace58 100644 --- a/spec/components/chat_message_updater_spec.rb +++ b/spec/components/chat_message_updater_spec.rb @@ -20,7 +20,7 @@ [admin1, admin2, user1, user2, user3, user4].each do |user| Fabricate(:user_chat_channel_membership, chat_channel: public_chat_channel, user: user) end - @direct_message_channel = DiscourseChat::DirectMessageChannelCreator.create([user1, user2]) + @direct_message_channel = DiscourseChat::DirectMessageChannelCreator.create!([user1, user2]) end def create_chat_message(user, message, channel, upload_ids: nil) diff --git a/spec/lib/direct_message_channel_creator_spec.rb b/spec/lib/direct_message_channel_creator_spec.rb new file mode 100644 index 000000000..b0ab6738d --- /dev/null +++ b/spec/lib/direct_message_channel_creator_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe DiscourseChat::DirectMessageChannelCreator do + fab!(:user_1) { Fabricate(:user) } + fab!(:user_2) { Fabricate(:user) } + + context 'existing direct message channel' do + fab!(:dm_chat_channel) { Fabricate(:chat_channel, chatable: Fabricate(:direct_message_channel, users: [user_1, user_2])) } + + it 'doesn’t create a new chat channel' do + expect { + subject.create!([user_1, user_2]) + }.to change { ChatChannel.count }.by(0) + end + end + + context 'non existing direct message channel' do + it 'creates a new chat channel' do + expect { + subject.create!([user_1, user_2]) + }.to change { ChatChannel.count }.by(1) + end + end +end diff --git a/spec/requests/chat_channel_controller_spec.rb b/spec/requests/chat_channel_controller_spec.rb index ff95b5bb8..cdc2a7f59 100644 --- a/spec/requests/chat_channel_controller_spec.rb +++ b/spec/requests/chat_channel_controller_spec.rb @@ -136,10 +136,10 @@ fab!(:user3) { Fabricate(:user) } before do - @dm1 = DiscourseChat::DirectMessageChannelCreator.create([user1, user2]) - @dm2 = DiscourseChat::DirectMessageChannelCreator.create([user1, user3]) - @dm3 = DiscourseChat::DirectMessageChannelCreator.create([user1, user2, user3]) - @dm4 = DiscourseChat::DirectMessageChannelCreator.create([user2, user3]) + @dm1 = DiscourseChat::DirectMessageChannelCreator.create!([user1, user2]) + @dm2 = DiscourseChat::DirectMessageChannelCreator.create!([user1, user3]) + @dm3 = DiscourseChat::DirectMessageChannelCreator.create!([user1, user2, user3]) + @dm4 = DiscourseChat::DirectMessageChannelCreator.create!([user2, user3]) end it "returns correct DMs for user1" do @@ -226,7 +226,7 @@ expect(response.status).to eq(200) end - it "errors when you try to unfollow a direct_message_channel" do + it "allows to unfollow a direct_message_channel" do sign_in(user) membership_record = UserChatChannelMembership.create!( chat_channel_id: dm_chat_channel.id, @@ -237,8 +237,8 @@ ) post "/chat/chat_channels/#{dm_chat_channel.id}/unfollow.json" - expect(response.status).to eq(422) - expect(membership_record.reload.following).to eq(true) + expect(response.status).to eq(200) + expect(membership_record.reload.following).to eq(false) end end diff --git a/spec/requests/chat_controller_spec.rb b/spec/requests/chat_controller_spec.rb index 9e0d4dea9..0a81b696b 100644 --- a/spec/requests/chat_controller_spec.rb +++ b/spec/requests/chat_controller_spec.rb @@ -185,6 +185,27 @@ expect(ChatMessage.last.message).to eq(message) end end + + describe 'for direct message' do + fab!(:user1) { Fabricate(:user) } + fab!(:user2) { Fabricate(:user) } + fab!(:chatable) { Fabricate(:direct_message_channel, users: [user1, user2]) } + fab!(:direct_message_channel) { Fabricate(:chat_channel, chatable: chatable) } + + it 'forces users to follow the channel' do + UserChatChannelMembership.create!(user: user1, chat_channel: direct_message_channel, following: true, desktop_notification_level: UserChatChannelMembership::NOTIFICATION_LEVELS[:always], mobile_notification_level: UserChatChannelMembership::NOTIFICATION_LEVELS[:always]) + UserChatChannelMembership.create!(user: user2, chat_channel: direct_message_channel, following: false, desktop_notification_level: UserChatChannelMembership::NOTIFICATION_LEVELS[:always], mobile_notification_level: UserChatChannelMembership::NOTIFICATION_LEVELS[:always]) + + expect(UserChatChannelMembership.find_by(user_id: user2.id).following).to eq(false) + + ChatPublisher.expects(:publish_new_direct_message_channel).once + + sign_in(user1) + post "/chat/#{direct_message_channel.id}.json", params: { message: message } + + expect(UserChatChannelMembership.find_by(user_id: user2.id).following).to eq(true) + end + end end describe "#edit_message" do