Show profile message actions for non-DM user profiles (#9496)

* Show profile message actions for non-DM user profiles

* Add DM profile visibility safeguards and coverage

* Simplify DM channel name parsing and add reversed order test

Remove redundant manual split('__') validation in
isDirectMessageWithViewedUser, relying on getUserIdFromChannelName
which already handles both name orderings. Add test for reversed
DM channel name order (B__A) to prevent regressions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Guillermo Vayá 2026-02-13 12:49:07 +01:00 committed by GitHub
parent f9001ec7cd
commit 9699318043
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 178 additions and 9 deletions

View file

@ -0,0 +1,73 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {General} from '@constants';
import {isDirectMessageWithViewedUser} from './index';
describe('screens/user_profile/index', () => {
it('should return true when viewing the DM counterpart user', () => {
const result = isDirectMessageWithViewedUser(
{
name: 'current_user_id__other_user_id',
type: General.DM_CHANNEL,
},
'current_user_id',
'other_user_id',
);
expect(result).toBe(true);
});
it('should return false when viewing a user different from the DM counterpart', () => {
const result = isDirectMessageWithViewedUser(
{
name: 'current_user_id__other_user_id',
type: General.DM_CHANNEL,
},
'current_user_id',
'someone_else',
);
expect(result).toBe(false);
});
it('should return false for non-DM channels', () => {
const result = isDirectMessageWithViewedUser(
{
name: 'current_user_id__other_user_id',
type: General.OPEN_CHANNEL,
},
'current_user_id',
'other_user_id',
);
expect(result).toBe(false);
});
it('should return true when DM channel name has reversed user order', () => {
const result = isDirectMessageWithViewedUser(
{
name: 'other_user_id__current_user_id',
type: General.DM_CHANNEL,
},
'current_user_id',
'other_user_id',
);
expect(result).toBe(true);
});
it('should return false when DM channel name is malformed', () => {
const result = isDirectMessageWithViewedUser(
{
name: 'malformed_channel_name',
type: General.DM_CHANNEL,
},
'current_user_id',
'other_user_id',
);
expect(result).toBe(false);
});
});

View file

@ -14,7 +14,7 @@ import {observeCanManageChannelMembers, observePermissionForChannel} from '@quer
import {observeConfigBooleanValue, observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system';
import {observeTeammateNameDisplay, observeCurrentUser, observeUser, observeUserIsChannelAdmin, observeUserIsTeamAdmin} from '@queries/servers/user';
import {isDefaultChannel} from '@utils/channel';
import {isSystemAdmin, sortCustomProfileAttributes, convertToAttributesMap, convertProfileAttributesToCustomAttributes} from '@utils/user';
import {isSystemAdmin, sortCustomProfileAttributes, convertToAttributesMap, convertProfileAttributesToCustomAttributes, getUserIdFromChannelName} from '@utils/user';
import UserProfile from './user_profile';
@ -26,6 +26,20 @@ type EnhancedProps = WithDatabaseArgs & {
channelId?: string;
}
type DMChannelInfo = {
name: string;
type: string;
};
export function isDirectMessageWithViewedUser(channel: DMChannelInfo|undefined, currentUserId: string, viewedUserId: string|undefined): boolean {
if (!channel || channel.type !== General.DM_CHANNEL || !viewedUserId) {
return false;
}
const dmUserId = getUserIdFromChannelName(currentUserId, channel.name);
return Boolean(dmUserId) && dmUserId === viewedUserId;
}
const enhanced = withObservables([], ({channelId, database, userId}: EnhancedProps) => {
const currentUser = observeCurrentUser(database);
const currentUserId = observeCurrentUserId(database);
@ -36,9 +50,9 @@ const enhanced = withObservables([], ({channelId, database, userId}: EnhancedPro
const isDC = channel ? channel.pipe(
switchMap((c) => of$(isDefaultChannel(c))),
) : of$(false);
const isDirectMessage = channelId ? channel.pipe(
switchMap((c) => of$(c?.type === General.DM_CHANNEL)),
) : of$(false);
const isDirectMessageWithUser = combineLatest([channel, currentUserId, user]).pipe(
map(([c, currentId, viewedUser]) => isDirectMessageWithViewedUser(c, currentId, viewedUser?.id)),
);
const teamId = channel.pipe(switchMap((c) => (c?.teamId ? of$(c.teamId) : observeCurrentTeamId(database))));
const isTeamAdmin = teamId.pipe(switchMap((id) => observeUserIsTeamAdmin(database, userId, id)));
const systemAdmin = user.pipe(switchMap((u) => of$(u?.roles ? isSystemAdmin(u.roles) : false)));
@ -81,7 +95,7 @@ const enhanced = withObservables([], ({channelId, database, userId}: EnhancedPro
isChannelAdmin,
isCustomStatusEnabled,
isDefaultChannel: isDC,
isDirectMessage,
isDirectMessageWithUser,
isMilitaryTime,
isSystemAdmin: systemAdmin,
isTeamAdmin,

View file

@ -0,0 +1,61 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shouldShowUserProfileOptions} from './user_profile';
describe('screens/user_profile/user_profile', () => {
it('should hide profile options when viewing the same user in a DM from channel context', () => {
const shouldShow = shouldShowUserProfileOptions({
channelContext: true,
isDirectMessageWithUser: true,
manageMode: false,
override: false,
});
expect(shouldShow).toBe(false);
});
it('should show profile options when viewing another user in a DM from channel context', () => {
const shouldShow = shouldShowUserProfileOptions({
channelContext: true,
isDirectMessageWithUser: false,
manageMode: false,
override: false,
});
expect(shouldShow).toBe(true);
});
it('should hide profile options when in manage mode', () => {
const shouldShow = shouldShowUserProfileOptions({
channelContext: true,
isDirectMessageWithUser: false,
manageMode: true,
override: false,
});
expect(shouldShow).toBe(false);
});
it('should hide profile options when override is enabled', () => {
const shouldShow = shouldShowUserProfileOptions({
channelContext: false,
isDirectMessageWithUser: false,
manageMode: false,
override: true,
});
expect(shouldShow).toBe(false);
});
it('should show profile options outside channel context even for direct message with viewed user', () => {
const shouldShow = shouldShowUserProfileOptions({
channelContext: false,
isDirectMessageWithUser: true,
manageMode: false,
override: false,
});
expect(shouldShow).toBe(true);
});
});

View file

@ -34,7 +34,7 @@ type Props = {
isChannelAdmin: boolean;
canManageAndRemoveMembers?: boolean;
isCustomStatusEnabled: boolean;
isDirectMessage: boolean;
isDirectMessageWithUser: boolean;
isDefaultChannel: boolean;
isMilitaryTime: boolean;
isSystemAdmin: boolean;
@ -66,6 +66,22 @@ const messages = defineMessages({
});
const channelContextScreens: AvailableScreens[] = [Screens.CHANNEL, Screens.THREAD];
type ShouldShowUserProfileOptionsProps = {
channelContext: boolean;
isDirectMessageWithUser: boolean;
manageMode: boolean;
override: boolean;
};
export function shouldShowUserProfileOptions({
channelContext,
isDirectMessageWithUser,
manageMode,
override,
}: ShouldShowUserProfileOptionsProps) {
return (!isDirectMessageWithUser || !channelContext) && !override && !manageMode;
}
const UserProfile = ({
canChangeMemberRoles,
canManageAndRemoveMembers,
@ -77,7 +93,7 @@ const UserProfile = ({
isChannelAdmin,
isCustomStatusEnabled,
isDefaultChannel,
isDirectMessage,
isDirectMessageWithUser,
isMilitaryTime,
isSystemAdmin,
isTeamAdmin,
@ -113,7 +129,12 @@ const UserProfile = ({
}
const showCustomStatus = isCustomStatusEnabled && Boolean(customStatus) && !user.isBot && !isCustomStatusExpired(user);
const showUserProfileOptions = (!isDirectMessage || !channelContext) && !override && !manageMode;
const showUserProfileOptions = shouldShowUserProfileOptions({
channelContext,
isDirectMessageWithUser,
manageMode,
override,
});
const showNickname = Boolean(user.nickname) && !override && !user.isBot && !manageMode;
const showPosition = Boolean(user.position) && !override && !user.isBot && !manageMode;
const showLocalTime = Boolean(localTime) && !override && !user.isBot && !manageMode;
@ -175,7 +196,7 @@ const UserProfile = ({
if (currentUserId !== user.id) {
fetchTeamAndChannelMembership(serverUrl, user.id, teamId, channelId);
}
}, []);
}, [channelId, currentUserId, serverUrl, teamId, user.id]);
const renderContent = () => {
return (