mirror of
https://github.com/discourse/discourse.git
synced 2025-05-26 04:01:52 +08:00
FIX: Error when leaving group DM channel (#31537)
Followup b5147a4634f0fd5c98262f949a8c766bfd73d290 When we aliased `leave` to `remove` and renamed the method in `DirectMessageChannel` in the previous commit, this inadvertantly caused an error when unfollowing group channels in the channel list. When clicking the X in the channel list, we hit ChannelsCurrentUserMembershipFollowsController for the current user and the channel, which is supposed to only unfollow the channel for all channel types including DMs. Group DMs have a different Leave behaviour vs Unfollow. Leaving the channel altogether is done from the channel settings page, the "Leave channel" button, and that deletes the user's membership and DM user record from that channel. So, we were trying to do the leave channel behaviour in the unfollow channel controller, which was returning the wrong record for the serializer (a User not a Membership) This fixes the issue and removes a bit of delegate/alias indirection which was making the code a bit harder to fllow and search, even though it was more succinct. Also adds missing specs that would have caught this regression.
This commit is contained in:
@ -51,7 +51,7 @@ module Jobs
|
|||||||
translation_key =
|
translation_key =
|
||||||
(
|
(
|
||||||
if @is_direct_message_channel
|
if @is_direct_message_channel
|
||||||
if @chat_channel.chatable.group
|
if @chat_channel.direct_message_group?
|
||||||
"discourse_push_notifications.popup.new_chat_message"
|
"discourse_push_notifications.popup.new_chat_message"
|
||||||
else
|
else
|
||||||
"discourse_push_notifications.popup.new_direct_chat_message"
|
"discourse_push_notifications.popup.new_direct_chat_message"
|
||||||
@ -63,7 +63,7 @@ module Jobs
|
|||||||
|
|
||||||
translation_args = { username: @creator.username }
|
translation_args = { username: @creator.username }
|
||||||
translation_args[:channel] = @chat_channel.title(user) unless @is_direct_message_channel &&
|
translation_args[:channel] = @chat_channel.title(user) unless @is_direct_message_channel &&
|
||||||
!@chat_channel.chatable.group
|
!@chat_channel.direct_message_group?
|
||||||
translation_args =
|
translation_args =
|
||||||
DiscoursePluginRegistry.apply_modifier(
|
DiscoursePluginRegistry.apply_modifier(
|
||||||
:chat_notification_translation_args,
|
:chat_notification_translation_args,
|
||||||
|
@ -144,7 +144,10 @@ module Chat
|
|||||||
def remove(user)
|
def remove(user)
|
||||||
Chat::ChannelMembershipManager.new(self).unfollow(user)
|
Chat::ChannelMembershipManager.new(self).unfollow(user)
|
||||||
end
|
end
|
||||||
alias leave remove
|
|
||||||
|
def leave(user)
|
||||||
|
self.remove(user)
|
||||||
|
end
|
||||||
|
|
||||||
def url
|
def url
|
||||||
"#{Discourse.base_url}/chat/c/#{self.slug || "-"}/#{self.id}"
|
"#{Discourse.base_url}/chat/c/#{self.slug || "-"}/#{self.id}"
|
||||||
|
@ -4,8 +4,6 @@ module Chat
|
|||||||
class DirectMessageChannel < Channel
|
class DirectMessageChannel < Channel
|
||||||
alias_method :direct_message, :chatable
|
alias_method :direct_message, :chatable
|
||||||
|
|
||||||
delegate :group?, to: :direct_message, prefix: true, allow_nil: true
|
|
||||||
|
|
||||||
before_validation(on: :create) { self.threading_enabled = true }
|
before_validation(on: :create) { self.threading_enabled = true }
|
||||||
|
|
||||||
def direct_message_channel?
|
def direct_message_channel?
|
||||||
@ -28,13 +26,17 @@ module Chat
|
|||||||
self.slug.blank?
|
self.slug.blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove(user)
|
# Group DMs are DMs with > 2 users
|
||||||
return super unless direct_message_group?
|
def direct_message_group?
|
||||||
|
direct_message.group?
|
||||||
|
end
|
||||||
|
|
||||||
|
def leave(user)
|
||||||
|
return super if !direct_message_group?
|
||||||
transaction do
|
transaction do
|
||||||
membership_for(user)&.destroy!
|
membership_for(user)&.destroy!
|
||||||
direct_message.users.delete(user)
|
direct_message.users.delete(user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
alias leave remove
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Chat
|
module Chat
|
||||||
# Service responsible to flag a message.
|
# Service responsible for completely leaving a channel,
|
||||||
|
# which does something different depending on the channel:
|
||||||
|
#
|
||||||
|
# Category channels - Unfollows the channel similar to
|
||||||
|
# behaviour of Chat::UnfollowChannel
|
||||||
|
# DM channels with 2 users - Same as category channel
|
||||||
|
# DM channels with > 2 users (group DM) - Deletes the user's
|
||||||
|
# membership and removes them from the channel's user list.
|
||||||
#
|
#
|
||||||
# @example
|
# @example
|
||||||
# ::Chat::LeaveChannel.call(
|
# ::Chat::LeaveChannel.call(
|
||||||
|
@ -57,8 +57,12 @@ module Chat
|
|||||||
end
|
end
|
||||||
|
|
||||||
def remove(channel:, target_user:)
|
def remove(channel:, target_user:)
|
||||||
|
if channel.direct_message_channel?
|
||||||
|
channel.leave(target_user)
|
||||||
|
else
|
||||||
channel.remove(target_user)
|
channel.remove(target_user)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def recompute_users_count(channel:)
|
def recompute_users_count(channel:)
|
||||||
channel.update!(
|
channel.update!(
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Chat
|
module Chat
|
||||||
# Service responsible to flag a message.
|
# Service responsible to unfollow a chat channel,
|
||||||
|
# which means you no longer receive notifications or
|
||||||
|
# see it in the channel list.
|
||||||
#
|
#
|
||||||
# @example
|
# @example
|
||||||
# ::Chat::UnfollowChannel.call(
|
# ::Chat::UnfollowChannel.call(
|
||||||
|
@ -35,10 +35,11 @@ end
|
|||||||
Fabricator(:direct_message_channel, from: :chat_channel) do
|
Fabricator(:direct_message_channel, from: :chat_channel) do
|
||||||
transient :users, :group, following: true, with_membership: true
|
transient :users, :group, following: true, with_membership: true
|
||||||
chatable do |attrs|
|
chatable do |attrs|
|
||||||
|
users = attrs[:users]
|
||||||
Fabricate(
|
Fabricate(
|
||||||
:direct_message,
|
:direct_message,
|
||||||
users: attrs[:users] || [Fabricate(:user), Fabricate(:user)],
|
users: users || [Fabricate(:user), Fabricate(:user)],
|
||||||
group: attrs[:group] || false,
|
group: attrs[:group] || (users ? users.length > 2 : false),
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
status { :open }
|
status { :open }
|
||||||
|
@ -271,7 +271,7 @@ RSpec.describe Jobs::Chat::NotifyWatching do
|
|||||||
|
|
||||||
context "for a direct message channel" do
|
context "for a direct message channel" do
|
||||||
fab!(:channel) do
|
fab!(:channel) do
|
||||||
Fabricate(:direct_message_channel, users: [user1, user2, user3], with_membership: false)
|
Fabricate(:direct_message_channel, users: [user1, user2], with_membership: false)
|
||||||
end
|
end
|
||||||
fab!(:membership1) do
|
fab!(:membership1) do
|
||||||
Fabricate(:user_chat_channel_membership, user: user1, chat_channel: channel)
|
Fabricate(:user_chat_channel_membership, user: user1, chat_channel: channel)
|
||||||
@ -279,11 +279,42 @@ RSpec.describe Jobs::Chat::NotifyWatching do
|
|||||||
fab!(:membership2) do
|
fab!(:membership2) do
|
||||||
Fabricate(:user_chat_channel_membership, user: user2, chat_channel: channel)
|
Fabricate(:user_chat_channel_membership, user: user2, chat_channel: channel)
|
||||||
end
|
end
|
||||||
|
fab!(:message) { Fabricate(:chat_message, chat_channel: channel, user: user1) }
|
||||||
|
|
||||||
|
context "when the channel is a group DM" do
|
||||||
fab!(:membership3) do
|
fab!(:membership3) do
|
||||||
Fabricate(:user_chat_channel_membership, user: user3, chat_channel: channel)
|
Fabricate(:user_chat_channel_membership, user: user3, chat_channel: channel)
|
||||||
end
|
end
|
||||||
fab!(:message) { Fabricate(:chat_message, chat_channel: channel, user: user1) }
|
|
||||||
|
|
||||||
|
before do
|
||||||
|
channel.add(user3)
|
||||||
|
channel.chatable.update!(group: true)
|
||||||
|
membership2.update!(
|
||||||
|
notification_level: Chat::UserChatChannelMembership::NOTIFICATION_LEVELS[:always],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sends a desktop notification" do
|
||||||
|
messages = notification_messages_for(user2)
|
||||||
|
|
||||||
|
expect(messages.first.data).to include(
|
||||||
|
{
|
||||||
|
username: user1.username,
|
||||||
|
notification_type: Notification.types[:chat_message],
|
||||||
|
post_url: message.url,
|
||||||
|
translated_title:
|
||||||
|
I18n.t(
|
||||||
|
"discourse_push_notifications.popup.new_chat_message",
|
||||||
|
{ username: user1.username, channel: channel.title(user2) },
|
||||||
|
),
|
||||||
|
tag: Chat::Notifier.push_notification_tag(:message, channel.id),
|
||||||
|
excerpt: message.push_notification_excerpt,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the channel is a regular DM" do
|
||||||
before do
|
before do
|
||||||
membership2.update!(
|
membership2.update!(
|
||||||
notification_level: Chat::UserChatChannelMembership::NOTIFICATION_LEVELS[:always],
|
notification_level: Chat::UserChatChannelMembership::NOTIFICATION_LEVELS[:always],
|
||||||
@ -301,13 +332,14 @@ RSpec.describe Jobs::Chat::NotifyWatching do
|
|||||||
translated_title:
|
translated_title:
|
||||||
I18n.t(
|
I18n.t(
|
||||||
"discourse_push_notifications.popup.new_direct_chat_message",
|
"discourse_push_notifications.popup.new_direct_chat_message",
|
||||||
{ username: user1.username, channel: channel.title(user2) },
|
{ username: user1.username },
|
||||||
),
|
),
|
||||||
tag: Chat::Notifier.push_notification_tag(:message, channel.id),
|
tag: Chat::Notifier.push_notification_tag(:message, channel.id),
|
||||||
excerpt: message.push_notification_excerpt,
|
excerpt: message.push_notification_excerpt,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "when the channel is muted via membership preferences" do
|
context "when the channel is muted via membership preferences" do
|
||||||
before { membership2.update!(muted: true) }
|
before { membership2.update!(muted: true) }
|
||||||
|
@ -89,12 +89,14 @@ RSpec.describe Chat::CategoryChannel do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "#leave" do
|
describe "#leave" do
|
||||||
let(:original_method) { channel.method(:remove) }
|
let(:public_category) { Fabricate(:category, read_restricted: false) }
|
||||||
let(:aliased_method) { channel.method(:leave) }
|
let(:channel) { Fabricate(:category_channel, chatable: public_category) }
|
||||||
|
|
||||||
it "is an alias to '#remove'" do
|
it "calls #remove" do
|
||||||
expect(original_method.original_name).to eq(aliased_method.original_name)
|
Fabricate(:user_chat_channel_membership, chat_channel: channel)
|
||||||
expect(original_method.source_location).to eq(aliased_method.source_location)
|
user = channel.user_chat_channel_memberships.first.user
|
||||||
|
channel.expects(:remove).with(user)
|
||||||
|
channel.leave(user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ RSpec.describe Chat::DirectMessageChannel do
|
|||||||
it_behaves_like "a chat channel model"
|
it_behaves_like "a chat channel model"
|
||||||
|
|
||||||
it { is_expected.to delegate_method(:allowed_user_ids).to(:direct_message).as(:user_ids) }
|
it { is_expected.to delegate_method(:allowed_user_ids).to(:direct_message).as(:user_ids) }
|
||||||
it { is_expected.to delegate_method(:group?).to(:direct_message).with_prefix.allow_nil }
|
|
||||||
it { is_expected.to validate_length_of(:description).is_at_most(500) }
|
it { is_expected.to validate_length_of(:description).is_at_most(500) }
|
||||||
it { is_expected.to validate_length_of(:chatable_type).is_at_most(100) }
|
it { is_expected.to validate_length_of(:chatable_type).is_at_most(100) }
|
||||||
it { is_expected.to validate_length_of(:type).is_at_most(100) }
|
it { is_expected.to validate_length_of(:type).is_at_most(100) }
|
||||||
@ -110,4 +109,15 @@ RSpec.describe Chat::DirectMessageChannel do
|
|||||||
expect(channel.slug).to eq(nil)
|
expect(channel.slug).to eq(nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#direct_message_group?" do
|
||||||
|
it "returns false if the DirectMessage chatable is not for a group DM" do
|
||||||
|
channel.chatable.update!(group: false)
|
||||||
|
expect(channel).not_to be_direct_message_group
|
||||||
|
end
|
||||||
|
it "returns true if the DirectMessage chatable is for a group DM" do
|
||||||
|
channel.chatable.update!(group: true)
|
||||||
|
expect(channel).to be_direct_message_group
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -34,7 +34,7 @@ describe Chat::Api::ChannelsCurrentUserMembershipController do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when channel is channel" do
|
context "when channel is a category channel" do
|
||||||
context "when current user can't write in channel" do
|
context "when current user can't write in channel" do
|
||||||
fab!(:private_category_1) { Fabricate(:private_category, group: Fabricate(:group)) }
|
fab!(:private_category_1) { Fabricate(:private_category, group: Fabricate(:group)) }
|
||||||
fab!(:readonly_group_1) { Fabricate(:group) }
|
fab!(:readonly_group_1) { Fabricate(:group) }
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
describe Chat::Api::ChannelsCurrentUserMembershipController do
|
describe Chat::Api::ChannelsCurrentUserMembershipController do
|
||||||
fab!(:current_user) { Fabricate(:user) }
|
fab!(:current_user) { Fabricate(:user) }
|
||||||
fab!(:channel_1) { Fabricate(:category_channel) }
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
channel_1.add(current_user)
|
channel_1.add(current_user)
|
||||||
@ -12,7 +11,33 @@ describe Chat::Api::ChannelsCurrentUserMembershipController do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "#destroy" do
|
describe "#destroy" do
|
||||||
describe "success" do
|
describe "for a category channel" do
|
||||||
|
fab!(:channel_1) { Fabricate(:category_channel) }
|
||||||
|
|
||||||
|
it "works" do
|
||||||
|
delete "/chat/api/channels/#{channel_1.id}/memberships/me/follows"
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(channel_1.membership_for(current_user).following).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when channel is not found" do
|
||||||
|
before { channel_1.destroy! }
|
||||||
|
it "returns a 404" do
|
||||||
|
delete "/chat/api/channels/-999/memberships/me/follows"
|
||||||
|
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for a group direct message channel" do
|
||||||
|
fab!(:other_user_1) { Fabricate(:user) }
|
||||||
|
fab!(:other_user_2) { Fabricate(:user) }
|
||||||
|
fab!(:channel_1) do
|
||||||
|
Fabricate(:direct_message_channel, users: [current_user, other_user_1, other_user_2])
|
||||||
|
end
|
||||||
|
|
||||||
it "works" do
|
it "works" do
|
||||||
delete "/chat/api/channels/#{channel_1.id}/memberships/me/follows"
|
delete "/chat/api/channels/#{channel_1.id}/memberships/me/follows"
|
||||||
|
|
||||||
@ -21,11 +46,15 @@ describe Chat::Api::ChannelsCurrentUserMembershipController do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when channel is not found" do
|
describe "for a direct message channel" do
|
||||||
it "returns a 404" do
|
fab!(:other_user_1) { Fabricate(:user) }
|
||||||
delete "/chat/api/channels/-999/memberships/me/follows"
|
fab!(:channel_1) { Fabricate(:direct_message_channel, users: [current_user, other_user_1]) }
|
||||||
|
|
||||||
expect(response.status).to eq(404)
|
it "works" do
|
||||||
|
delete "/chat/api/channels/#{channel_1.id}/memberships/me/follows"
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(channel_1.membership_for(current_user).following).to eq(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user