diff --git a/server/channels/app/notification.go b/server/channels/app/notification.go index 6a35a65fbf0..0927820acdf 100644 --- a/server/channels/app/notification.go +++ b/server/channels/app/notification.go @@ -1260,6 +1260,7 @@ func (a *App) filterOutOfChannelMentions(c request.CTX, sender *model.User, post // Differentiate between mentionedUsersInTheTeam who can and can't be added to the channel var outOfChannelUsers model.UserSlice var outOfGroupsUsers model.UserSlice + if channel.IsGroupConstrained() { nonMemberIDs, err := a.FilterNonGroupChannelMembers(teamUsers.IDs(), channel) if err != nil { @@ -1289,6 +1290,8 @@ func makeOutOfChannelMentionPost(sender *model.User, post *model.Post, outOfChan ephemeralPostId := model.NewId() var message string + + // Generate message for users who can be invited if len(outOfChannelUsers) == 1 { message = T("api.post.check_for_out_of_channel_mentions.message.one", map[string]any{ "Username": ocUsernames[0], diff --git a/webapp/channels/src/components/post_view/post_add_channel_member/__snapshots__/post_add_channel_member.test.tsx.snap b/webapp/channels/src/components/post_view/post_add_channel_member/__snapshots__/post_add_channel_member.test.tsx.snap index 4348f635cec..b21630c4d3d 100644 --- a/webapp/channels/src/components/post_view/post_add_channel_member/__snapshots__/post_add_channel_member.test.tsx.snap +++ b/webapp/channels/src/components/post_view/post_add_channel_member/__snapshots__/post_add_channel_member.test.tsx.snap @@ -6,7 +6,9 @@ exports[`components/post_view/PostAddChannelMember should match snapshot, empty exports[`components/post_view/PostAddChannelMember should match snapshot, more than 3 users 1`] = ` -

+

-

+

-

+

-

+

`; +exports[`components/post_view/PostAddChannelMember should match snapshot, with ABAC policy enforced 1`] = ` +

+ + + + , + + + + + + + did not get notified by this mention because they are not in the channel. +

+`; + exports[`components/post_view/PostAddChannelMember should match snapshot, with no-groups usernames 1`] = ` -

+

-

+

{ addChannelMember: jest.fn(), }, noGroupsUsernames: [], + isPolicyEnforced: false, }; test('should match snapshot, empty postId', () => { @@ -140,4 +141,39 @@ describe('components/post_view/PostAddChannelMember', () => { const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); + + test('should match snapshot, with ABAC policy enforced', () => { + const props: Props = { + ...requiredProps, + usernames: ['username_1', 'username_2', 'username_3'], + isPolicyEnforced: true, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); + + test('should never show invite links when policy is enforced (ABAC channels)', () => { + const props: Props = { + ...requiredProps, + usernames: ['username_1', 'username_2'], + noGroupsUsernames: [], + isPolicyEnforced: true, + }; + const wrapper = shallow(); + expect(wrapper.find('.PostBody_addChannelMemberLink')).toHaveLength(0); + }); + + test('should show single consolidated message for ABAC channels regardless of user types', () => { + const props: Props = { + ...requiredProps, + usernames: ['user1', 'user2'], + noGroupsUsernames: ['user3'], + isPolicyEnforced: true, + }; + const wrapper = shallow(); + + // Should render only one consolidated message with no invite links + expect(wrapper.find('p')).toHaveLength(1); + expect(wrapper.find('.PostBody_addChannelMemberLink')).toHaveLength(0); + }); }); diff --git a/webapp/channels/src/components/post_view/post_add_channel_member/post_add_channel_member.tsx b/webapp/channels/src/components/post_view/post_add_channel_member/post_add_channel_member.tsx index 0271e175510..59281f38fa7 100644 --- a/webapp/channels/src/components/post_view/post_add_channel_member/post_add_channel_member.tsx +++ b/webapp/channels/src/components/post_view/post_add_channel_member/post_add_channel_member.tsx @@ -26,6 +26,7 @@ export interface Props { userIds: string[]; usernames: string[]; noGroupsUsernames: string[]; + isPolicyEnforced: boolean; actions: Actions; } @@ -146,11 +147,33 @@ export default class PostAddChannelMember extends React.PureComponent + {allUsersAtMentions} + {' '} + {messageText} +

+ ); + } + + // Regular flow for non-ABAC channels let link; if (channelType === Constants.PRIVATE_CHANNEL) { link = ( @@ -168,44 +191,32 @@ export default class PostAddChannelMember extends React.PureComponent !noGroupsUsernames.includes(username)); + const outOfGroupsUsers = noGroupsUsernames; + + const messages = []; + + // Handle invitable users with invite functionality + if (invitableUsers.length > 0) { + const invitableAtMentions = this.generateAtMentions(invitableUsers); + const invitableMessagePart = invitableUsers.length === 1 ? ( - ); - } else if (usernames.length > 1) { - outOfChannelMessagePart = ( + ) : ( ); - } - let outOfGroupsMessagePart; - const outOfGroupsAtMentions = this.generateAtMentions(noGroupsUsernames); - if (noGroupsUsernames.length) { - outOfGroupsMessagePart = ( - - ); - } - - let outOfChannelMessage = null; - let outOfGroupsMessage = null; - - if (usernames.length) { - outOfChannelMessage = ( -

- {outOfChannelAtMentions} + messages.push( +

+ {invitableAtMentions} {' '} - {outOfChannelMessagePart} + {invitableMessagePart} -

+

, ); } - if (noGroupsUsernames.length) { - outOfGroupsMessage = ( -

+ // Handle users not in required groups with specific messaging + if (outOfGroupsUsers.length > 0) { + const outOfGroupsAtMentions = this.generateAtMentions(outOfGroupsUsers); + const outOfGroupsMessagePart = ( + + ); + + messages.push( +

{outOfGroupsAtMentions} {' '} {outOfGroupsMessagePart} -

+

, ); } return ( <> - {outOfChannelMessage} - {outOfGroupsMessage} + {messages} ); }