Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: show only relevant userInfoActions for mentioned non-members #31525

Open
wants to merge 55 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
12a9094
added findByUsernameAndRoom to User model
abhinavkrin Jan 24, 2024
65f0e0e
added groups/channels/im membercheck endpoints
abhinavkrin Jan 24, 2024
432d554
show only relevant userInfoActions for non-members
abhinavkrin Jan 24, 2024
2a58c44
added changeset
abhinavkrin Jan 24, 2024
f848c67
added ajv validation
abhinavkrin Feb 1, 2024
aa8aaab
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Feb 1, 2024
8b07031
remove check()
abhinavkrin Feb 1, 2024
c1580f4
replaced memberExists endpoint with subscription.exists
abhinavkrin Feb 7, 2024
0dc3079
use subscriptions.exist endpoint
abhinavkrin Feb 7, 2024
d78888b
Update packages/rest-typings/src/v1/dm/im.ts
abhinavkrin Feb 16, 2024
9e42919
Update apps/meteor/app/api/server/v1/subscriptions.ts
abhinavkrin Feb 16, 2024
e126fc8
Update apps/meteor/app/api/server/v1/subscriptions.ts
abhinavkrin Feb 16, 2024
9abf6c2
Update packages/rest-typings/src/v1/subscriptionsEndpoints.ts
abhinavkrin Feb 16, 2024
5143108
Update apps/meteor/client/views/hooks/useMemberExists.ts
abhinavkrin Feb 16, 2024
fafbaff
minor changes and subscriptions.exists -> rooms.isMember
abhinavkrin Feb 16, 2024
b663025
unit test for rooms.isMember
abhinavkrin Feb 16, 2024
c828dd9
use api endpoint
abhinavkrin Feb 17, 2024
0cc0471
improve api test
abhinavkrin Feb 17, 2024
ecf3cc1
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Feb 17, 2024
8aa68fc
updated message for add to room userinfo action
abhinavkrin Feb 19, 2024
4327cf2
Merge branch 'develop' into fix/mention-user-membership-check
scuciatto Feb 20, 2024
db52395
added tests
abhinavkrin Feb 21, 2024
8c363e2
updated rooms.isMember endpoint and its test
abhinavkrin Feb 21, 2024
cb61119
updated test 09-rooms.js
abhinavkrin Feb 22, 2024
5386bf4
Update useMemberExists.ts
abhinavkrin Feb 22, 2024
3db764d
minor fixes
abhinavkrin Feb 23, 2024
5f9e7f5
minor fixes
abhinavkrin Feb 23, 2024
8e9b4b7
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Feb 23, 2024
68b2bb1
minor fixes
abhinavkrin Feb 23, 2024
50cd2c0
Update apps/meteor/tests/end-to-end/api/09-rooms.js
abhinavkrin Feb 27, 2024
dfe9eb8
fixed test 09-rooms.js
abhinavkrin Feb 27, 2024
eac77f2
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Feb 27, 2024
dde4c9a
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into fix/…
abhinavkrin Mar 1, 2024
eb86620
Merge branch 'develop' into fix/mention-user-membership-check
matheusbsilva137 Mar 4, 2024
7d142b1
improved useMemberExists hook
abhinavkrin Mar 13, 2024
76a71ee
updated useMemberExists
abhinavkrin Mar 14, 2024
d3f53ff
fix review
yash-rajpal Mar 15, 2024
910b67c
minor fix
abhinavkrin Mar 15, 2024
5b2b67e
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Mar 19, 2024
172ad7a
fixed loading to reflect ui
abhinavkrin Mar 27, 2024
5812bad
merge develop
abhinavkrin Jun 12, 2024
8e7de27
added ui e2e tests
abhinavkrin Jun 13, 2024
852d304
improved tests
abhinavkrin Jun 13, 2024
5c383ec
minor refactor
abhinavkrin Jun 18, 2024
c751576
Merge branch 'develop' into fix/mention-user-membership-check
d-gubert Jun 18, 2024
5f8bb47
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Jun 19, 2024
9e17053
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Jun 19, 2024
2beeb77
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Jun 20, 2024
10483e2
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Jun 21, 2024
b68f6ab
minor fixes and improved tests
abhinavkrin Jun 21, 2024
1cee4b2
modified rooms.isMember response
abhinavkrin Jun 25, 2024
a2176ad
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into fix/…
abhinavkrin Jun 25, 2024
01b1a69
fixed types
abhinavkrin Jun 25, 2024
fdad230
fix typo
abhinavkrin Jun 28, 2024
ee18257
merge develop
abhinavkrin Jul 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/ninety-hounds-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/rest-typings': patch
'@rocket.chat/meteor': patch
'@rocket.chat/i18n': patch
---

Fix: Show correct user info actions for non-members in channels.
40 changes: 38 additions & 2 deletions apps/meteor/app/api/server/v1/rooms.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { Media } from '@rocket.chat/core-services';
import type { IRoom, IUpload } from '@rocket.chat/core-typings';
import { Messages, Rooms, Users, Uploads } from '@rocket.chat/models';
import { Messages, Rooms, Users, Uploads, Subscriptions } from '@rocket.chat/models';
import type { Notifications } from '@rocket.chat/rest-typings';
import { isGETRoomsNameExists, isRoomsImagesProps, isRoomsMuteUnmuteUserProps, isRoomsExportProps } from '@rocket.chat/rest-typings';
import {
isGETRoomsNameExists,
isRoomsImagesProps,
isRoomsMuteUnmuteUserProps,
isRoomsExportProps,
isRoomsIsMemberProps,
} from '@rocket.chat/rest-typings';
import { Meteor } from 'meteor/meteor';

import { isTruthy } from '../../../../lib/isTruthy';
Expand Down Expand Up @@ -783,6 +789,36 @@ API.v1.addRoute(
},
);

API.v1.addRoute(
'rooms.isMember',
{
authRequired: true,
validateParams: isRoomsIsMemberProps,
},
{
async get() {
const { roomId, userId, username } = this.queryParams;
const [room, user] = await Promise.all([
findRoomByIdOrName({
params: { roomId },
}) as Promise<IRoom>,
Users.findOneByIdOrUsername(userId || username),
]);

if (!user?._id) {
return API.v1.failure('error-user-not-found');
}

if (await canAccessRoomAsync(room, { _id: this.user._id })) {
return API.v1.success({
isMember: (await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) > 0,
});
}
return API.v1.unauthorized();
},
},
);

API.v1.addRoute(
'rooms.muteUser',
{ authRequired: true, validateParams: isRoomsMuteUnmuteUserProps },
Expand Down
10 changes: 10 additions & 0 deletions apps/meteor/client/views/hooks/useMemberExists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';

type UseMemberExistsProps = { roomId: string; username: string };

export const useMemberExists = ({ roomId, username }: UseMemberExistsProps) => {
const checkMember = useEndpoint('GET', '/v1/rooms.isMember');

return useQuery(['rooms/isMember', roomId, username], () => checkMember({ roomId, username }));
};
15 changes: 14 additions & 1 deletion apps/meteor/client/views/room/UserCard/UserCardWithData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import LocalTime from '../../../components/LocalTime';
import { UserCard, UserCardAction, UserCardRole, UserCardSkeleton } from '../../../components/UserCard';
import { ReactiveUserStatus } from '../../../components/UserStatus';
import { useUserInfoQuery } from '../../../hooks/useUserInfoQuery';
import { useMemberExists } from '../../hooks/useMemberExists';
import { useUserInfoActions } from '../hooks/useUserInfoActions';

type UserCardWithDataProps = {
Expand All @@ -24,7 +25,16 @@ const UserCardWithData = ({ username, rid, onOpenUserInfo, onClose }: UserCardWi
const getRoles = useRolesDescription();
const showRealNames = Boolean(useSetting('UI_Use_Real_Name'));

const { data, isLoading } = useUserInfoQuery({ username });
const { data, isLoading: isUserInfoLoading } = useUserInfoQuery({ username });
const {
data: isMemberData,
refetch,
isSuccess: membershipCheckSuccess,
isLoading: isMembershipStatusLoading,
} = useMemberExists({ roomId: rid, username });

const isLoading = isUserInfoLoading || isMembershipStatusLoading;
const isMember = membershipCheckSuccess && isMemberData?.isMember;

const user = useMemo(() => {
const defaultValue = isLoading ? undefined : null;
Expand Down Expand Up @@ -62,6 +72,9 @@ const UserCardWithData = ({ username, rid, onOpenUserInfo, onClose }: UserCardWi
const { actions: actionsDefinition, menuActions: menuOptions } = useUserInfoActions(
{ _id: user._id ?? '', username: user.username, name: user.name },
rid,
refetch,
undefined,
isMember,
);

const menu = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type RoomMembersActionsProps = {

const RoomMembersActions = ({ username, _id, name, rid, reload }: RoomMembersActionsProps): ReactElement | null => {
const t = useTranslation();
const { menuActions: menuOptions } = useUserInfoActions({ _id, username, name }, rid, reload, 0);
const { menuActions: menuOptions } = useUserInfoActions({ _id, username, name }, rid, reload, 0, true);
if (!menuOptions) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/* eslint-disable react/display-name, react/no-multi-comp */
import type { IRoom, IUser } from '@rocket.chat/core-typings';
import { ButtonGroup, IconButton } from '@rocket.chat/fuselage';
import { ButtonGroup, IconButton, Skeleton } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { useMemo } from 'react';

import GenericMenu from '../../../../components/GenericMenu/GenericMenu';
import { UserInfoAction } from '../../../../components/UserInfo';
import { useMemberExists } from '../../../hooks/useMemberExists';
import { useUserInfoActions } from '../../hooks/useUserInfoActions';

type UserInfoActionsProps = {
Expand All @@ -17,10 +18,24 @@ type UserInfoActionsProps = {

const UserInfoActions = ({ user, rid, backToList }: UserInfoActionsProps): ReactElement => {
const t = useTranslation();
const {
data: isMemberData,
refetch,
isSuccess: membershipCheckSuccess,
isLoading,
} = useMemberExists({ roomId: rid, username: user.username as string });

const isMember = membershipCheckSuccess && isMemberData?.isMember;

const { actions: actionsDefinition, menuActions: menuOptions } = useUserInfoActions(
{ _id: user._id, username: user.username, name: user.name },
rid,
backToList,
() => {
backToList?.();
refetch();
},
undefined,
isMember,
);

const menu = useMemo(() => {
Expand Down Expand Up @@ -51,6 +66,9 @@ const UserInfoActions = ({ user, rid, backToList }: UserInfoActionsProps): React
return [...actionsDefinition.map(mapAction), menu].filter(Boolean);
}, [actionsDefinition, menu]);

if (isLoading) {
return <Skeleton w='full' />;
}
return <ButtonGroup align='center'>{actions}</ButtonGroup>;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { IRoom, IUser } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import {
useTranslation,
useUser,
useUserRoom,
useUserSubscription,
useToastMessageDispatch,
useAtLeastOnePermission,
useEndpoint,
} from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';

import * as Federation from '../../../../../lib/federation/Federation';
import { useAddMatrixUsers } from '../../../contextualBar/RoomMembers/AddUsers/AddMatrixUsers/useAddMatrixUsers';
import { getRoomDirectives } from '../../../lib/getRoomDirectives';
import type { UserInfoAction } from '../useUserInfoActions';

const inviteUserEndpoints = {
c: '/v1/channels.invite',
p: '/v1/groups.invite',
} as const;

export const useAddUserAction = (
user: Pick<IUser, '_id' | 'username'>,
rid: IRoom['_id'],
reload?: () => void,
): UserInfoAction | undefined => {
const t = useTranslation();
const room = useUserRoom(rid);
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
const currentUser = useUser();
const subscription = useUserSubscription(rid);
const dispatchToastMessage = useToastMessageDispatch();

const { username, _id: uid } = user;

if (!room) {
throw Error('Room not provided');
}

const hasPermissionToAddUsers = useAtLeastOnePermission(
useMemo(() => [room?.t === 'p' ? 'add-user-to-any-p-room' : 'add-user-to-any-c-room', 'add-user-to-joined-room'], [room?.t]),
rid,
);

const userCanAdd =
room && user && isRoomFederated(room)
? Federation.isEditableByTheUser(currentUser || undefined, room, subscription)
: hasPermissionToAddUsers;

const { roomCanInvite } = getRoomDirectives({ room, showingUserId: uid, userSubscription: subscription });

const inviteUser = useEndpoint('POST', inviteUserEndpoints[room.t === 'p' ? 'p' : 'c']);

const handleAddUser = useEffectEvent(async ({ users }) => {
const [username] = users;
await inviteUser({ roomId: rid, username });
reload?.();
});

const addClickHandler = useAddMatrixUsers();

const addUserOptionAction = useEffectEvent(async () => {
try {
const users = [username as string];
if (isRoomFederated(room)) {
addClickHandler.mutate({
users,
handleSave: handleAddUser,
});
} else {
await handleAddUser({ users });
}
dispatchToastMessage({ type: 'success', message: t('User_added') });
} catch (error) {
dispatchToastMessage({ type: 'error', message: error as Error });
}
});

const addUserOption = useMemo(
() =>
roomCanInvite && userCanAdd && room.archived !== true
? {
content: t('add-to-room'),
icon: 'user-plus' as const,
onClick: addUserOptionAction,
type: 'management' as const,
}
: undefined,
[roomCanInvite, userCanAdd, room.archived, t, addUserOptionAction],
);

return addUserOption;
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useMemo } from 'react';

import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem';
import { useEmbeddedLayout } from '../../../../hooks/useEmbeddedLayout';
import { useAddUserAction } from './actions/useAddUserAction';
import { useBlockUserAction } from './actions/useBlockUserAction';
import { useCallAction } from './actions/useCallAction';
import { useChangeLeaderAction } from './actions/useChangeLeaderAction';
Expand Down Expand Up @@ -39,7 +40,9 @@ export const useUserInfoActions = (
rid: IRoom['_id'],
reload?: () => void,
size = 2,
isMember?: boolean,
): { actions: [string, UserInfoAction][]; menuActions: any | undefined } => {
const addUser = useAddUserAction(user, rid, reload);
const blockUser = useBlockUserAction(user, rid);
const changeLeader = useChangeLeaderAction(user, rid);
const changeModerator = useChangeModeratorAction(user, rid);
Expand All @@ -58,15 +61,16 @@ export const useUserInfoActions = (
() => ({
...(openDirectMessage && !isLayoutEmbedded && { openDirectMessage }),
...(call && { call }),
...(changeOwner && { changeOwner }),
...(changeLeader && { changeLeader }),
...(changeModerator && { changeModerator }),
...(openModerationConsole && { openModerationConsole }),
...(ignoreUser && { ignoreUser }),
...(muteUser && { muteUser }),
...(!isMember && addUser && { addUser }),
...(isMember && changeOwner && { changeOwner }),
...(isMember && changeLeader && { changeLeader }),
...(isMember && changeModerator && { changeModerator }),
...(isMember && openModerationConsole && { openModerationConsole }),
...(isMember && ignoreUser && { ignoreUser }),
...(isMember && muteUser && { muteUser }),
...(blockUser && { toggleBlock: blockUser }),
...(reportUserOption && { reportUser: reportUserOption }),
...(removeUser && { removeUser }),
...(isMember && removeUser && { removeUser }),
}),
[
openDirectMessage,
Expand All @@ -81,6 +85,8 @@ export const useUserInfoActions = (
removeUser,
reportUserOption,
openModerationConsole,
addUser,
isMember,
],
);

Expand Down
6 changes: 4 additions & 2 deletions apps/meteor/client/views/room/lib/getRoomDirectives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type getRoomDirectiesType = {
roomCanBlock: boolean;
roomCanMute: boolean;
roomCanRemove: boolean;
roomCanInvite: boolean;
};

export const getRoomDirectives = ({
Expand All @@ -24,7 +25,7 @@ export const getRoomDirectives = ({
}): getRoomDirectiesType => {
const roomDirectives = room?.t && roomCoordinator.getRoomDirectives(room.t);

const [roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove] = [
const [roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove, roomCanInvite] = [
...((roomDirectives && [
roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_OWNER, showingUserId, userSubscription),
roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_LEADER, showingUserId, userSubscription),
Expand All @@ -33,9 +34,10 @@ export const getRoomDirectives = ({
roomDirectives.allowMemberAction(room, RoomMemberActions.BLOCK, showingUserId, userSubscription),
roomDirectives.allowMemberAction(room, RoomMemberActions.MUTE, showingUserId, userSubscription),
roomDirectives.allowMemberAction(room, RoomMemberActions.REMOVE_USER, showingUserId, userSubscription),
roomDirectives.allowMemberAction(room, RoomMemberActions.INVITE, showingUserId, userSubscription),
]) ??
[]),
];

return { roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove };
return { roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove, roomCanInvite };
};
Loading
Loading