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(meteor): Respect UI_Use_Real_Name setting for reactions #30069

Merged
merged 20 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/seven-pugs-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Fixed using real names on messages reactions
26 changes: 26 additions & 0 deletions apps/meteor/app/api/server/v1/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
isUsersSetPreferencesParamsPOST,
isUsersCheckUsernameAvailabilityParamsGET,
isUsersSendConfirmationEmailParamsPOST,
isUsersGetNamesParamsGETProps,
} from '@rocket.chat/rest-typings';
import { Accounts } from 'meteor/accounts-base';
import { Match, check } from 'meteor/check';
Expand Down Expand Up @@ -1255,6 +1256,31 @@ API.v1.addRoute(
},
);

API.v1.addRoute(
'users.getNames',
yash-rajpal marked this conversation as resolved.
Show resolved Hide resolved
{ authRequired: true, validateParams: isUsersGetNamesParamsGETProps },
{
async get() {
const options = {
projection: {
name: 1,
username: 1,
},
};

if ('userIds' in this.queryParams) {
return API.v1.success({
users: await Users.findByIds(this.queryParams.userIds, options).toArray(),
});
}

return API.v1.success({
users: await Users.findByUsernames(this.queryParams.usernames, options).toArray(),
});
},
},
);

settings.watch<number>('Rate_Limiter_Limit_RegisterUser', (value) => {
const userRegisterRoute = '/api/v1/users.registerpost';

Expand Down
7 changes: 4 additions & 3 deletions apps/meteor/client/components/message/content/Reactions.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { IMessage } from '@rocket.chat/core-typings';
import { MessageReactions, MessageReactionAction } from '@rocket.chat/fuselage';
import { useUser } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';

import { useOpenEmojiPicker, useReactionsFilter, useUserHasReacted } from '../list/MessageListContext';
import { useOpenEmojiPicker, useUserHasReacted } from '../list/MessageListContext';
import Reaction from './reactions/Reaction';
import { useToggleReactionMutation } from './reactions/useToggleReactionMutation';

Expand All @@ -13,8 +14,8 @@ type ReactionsProps = {

const Reactions = ({ message }: ReactionsProps): ReactElement => {
const hasReacted = useUserHasReacted(message);
const filterReactions = useReactionsFilter(message);
const openEmojiPicker = useOpenEmojiPicker(message);
const username = useUser()?.username;

const toggleReactionMutation = useToggleReactionMutation();

Expand All @@ -27,7 +28,7 @@ const Reactions = ({ message }: ReactionsProps): ReactElement => {
counter={reactions.usernames.length}
hasReacted={hasReacted}
name={name}
names={filterReactions(name)}
names={reactions?.usernames.filter((user) => user !== username) || []}
onClick={() => toggleReactionMutation.mutate({ mid: message._id, reaction: name })}
/>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { MessageReaction as MessageReactionTemplate, MessageReactionEmoji, MessageReactionCounter } from '@rocket.chat/fuselage';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useTooltipClose, useTooltipOpen, useTranslation } from '@rocket.chat/ui-contexts';
import { useEndpoint, useTooltipClose, useTooltipOpen, useTranslation } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type { ReactElement } from 'react';
import React, { useRef } from 'react';
import React, { useContext, useRef } from 'react';

import { getEmojiClassNameAndDataTitle } from '../../../../lib/utils/renderEmoji';
import MarkdownText from '../../../MarkdownText';
import { MessageListContext } from '../../list/MessageListContext';

// TODO: replace it with proper usage of i18next plurals
const getTranslationKey = (users: string[], mine: boolean): TranslationKey => {
Expand Down Expand Up @@ -41,29 +43,53 @@ const Reaction = ({ hasReacted, counter, name, names, ...props }: ReactionProps)
const ref = useRef<HTMLDivElement>(null);
const openTooltip = useTooltipOpen();
const closeTooltip = useTooltipClose();
const { showRealName } = useContext(MessageListContext);

const mine = hasReacted(name);

const key = getTranslationKey(names, mine);

const emojiProps = getEmojiClassNameAndDataTitle(name);

const getNames = useEndpoint('GET', '/v1/users.getNames');

const { refetch } = useQuery(
['users.getNames', names],
async () => {
if (names.length === 0) {
return undefined;
}

const users: string[] = showRealName
? (await getNames({ usernames: names }))?.users?.map((user) => user.name || '') || names.map((name) => `@${name}`)
: names.map((name) => `@${name}`);

return users;
},
{
enabled: false,
},
);

return (
<MessageReactionTemplate
ref={ref}
key={name}
mine={mine}
tabIndex={0}
role='button'
onMouseOver={(e): void => {
onMouseOver={async (e) => {
e.stopPropagation();
e.preventDefault();

const users = (await refetch()).data;

ref.current &&
openTooltip(
<MarkdownText
content={t(key, {
counter: names.length > 10 ? names.length - 10 : names.length,
users: names.slice(0, 10).join(', '),
users: users?.slice(0, 10).join(', ') || '',
emoji: name,
})}
variant='inline'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export type MessageListContextValue = {
useShowFollowing: ({ message }: { message: IMessage }) => boolean;
useMessageDateFormatter: () => (date: Date) => string;
useUserHasReacted: (message: IMessage) => (reaction: string) => boolean;
useReactionsFilter: (message: IMessage) => (reaction: string) => string[];
useOpenEmojiPicker: (message: IMessage) => (event: React.MouseEvent) => void;
showRoles: boolean;
showRealName: boolean;
Expand Down Expand Up @@ -38,10 +37,6 @@ export const MessageListContext = createContext<MessageListContextValue>({
(date: Date): string =>
date.toString(),
useOpenEmojiPicker: () => (): void => undefined,
useReactionsFilter:
(message) =>
(reaction: string): string[] =>
message.reactions ? message.reactions[reaction]?.usernames || [] : [],
showRoles: false,
showRealName: false,
showUsername: false,
Expand Down Expand Up @@ -69,5 +64,3 @@ export const useUserHasReacted: MessageListContextValue['useUserHasReacted'] = (
useContext(MessageListContext).useUserHasReacted(message);
export const useOpenEmojiPicker: MessageListContextValue['useOpenEmojiPicker'] = (...args) =>
useContext(MessageListContext).useOpenEmojiPicker(...args);
export const useReactionsFilter: MessageListContextValue['useReactionsFilter'] = (message: IMessage) =>
useContext(MessageListContext).useReactionsFilter(message);
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { IMessage } from '@rocket.chat/core-typings';
import { isMessageReactionsNormalized, isThreadMainMessage } from '@rocket.chat/core-typings';
import { isThreadMainMessage } from '@rocket.chat/core-typings';
import { useLayout, useUser, useUserPreference, useSetting, useEndpoint, useSearchParameter } from '@rocket.chat/ui-contexts';
import type { VFC, ReactNode } from 'react';
import React, { useMemo, memo } from 'react';
Expand Down Expand Up @@ -60,29 +59,6 @@ const MessageListProvider: VFC<MessageListProviderProps> = ({ children, scrollMe
const context: MessageListContextValue = useMemo(
() => ({
showColors,
useReactionsFilter: (message: IMessage): ((reaction: string) => string[]) => {
const { reactions } = message;
return !showRealName
? (reaction: string): string[] =>
reactions?.[reaction]?.usernames.filter((user) => user !== username).map((username) => `@${username}`) || []
: (reaction: string): string[] => {
if (!reactions?.[reaction]) {
return [];
}
if (!isMessageReactionsNormalized(message)) {
return message.reactions?.[reaction]?.usernames.filter((user) => user !== username).map((username) => `@${username}`) || [];
}
if (!username) {
return message.reactions[reaction].names;
}
const index = message.reactions[reaction].usernames.indexOf(username);
if (index === -1) {
return message.reactions[reaction].names;
}

return message.reactions[reaction].names.splice(index, 1);
};
},
useUserHasReacted: username
? (message) =>
(reaction): boolean =>
Expand Down
3 changes: 0 additions & 3 deletions packages/core-typings/src/IMessage/IMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,6 @@ export interface IMessageReactionsNormalized extends IMessage {
};
}

export const isMessageReactionsNormalized = (message: IMessage): message is IMessageReactionsNormalized =>
Boolean('reactions' in message && message.reactions && message.reactions[0] && 'names' in message.reactions[0]);

export interface IOmnichannelSystemMessage extends IMessage {
navigation?: {
page: {
Expand Down
8 changes: 8 additions & 0 deletions packages/rest-typings/src/v1/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { UserLogoutParamsPOST } from './users/UserLogoutParamsPOST';
import type { UserRegisterParamsPOST } from './users/UserRegisterParamsPOST';
import type { UserSetActiveStatusParamsPOST } from './users/UserSetActiveStatusParamsPOST';
import type { UsersAutocompleteParamsGET } from './users/UsersAutocompleteParamsGET';
import type { UsersGetNamesParamsGET } from './users/UsersGetNamesParamsGET';
import type { UsersInfoParamsGet } from './users/UsersInfoParamsGet';
import type { UsersListTeamsParamsGET } from './users/UsersListTeamsParamsGET';
import type { UsersSendConfirmationEmailParamsPOST } from './users/UsersSendConfirmationEmailParamsPOST';
Expand Down Expand Up @@ -367,6 +368,12 @@ export type UsersEndpoints = {
'/v1/users.deleteOwnAccount': {
POST: (params: { password: string; confirmRelinquish?: boolean }) => void;
};

'/v1/users.getNames': {
GET: (params: UsersGetNamesParamsGET) => {
users: Pick<IUser, '_id' | 'username' | 'name'>[];
};
};
};

export * from './users/UserCreateParamsPOST';
Expand All @@ -377,3 +384,4 @@ export * from './users/UserRegisterParamsPOST';
export * from './users/UserLogoutParamsPOST';
export * from './users/UsersListTeamsParamsGET';
export * from './users/UsersAutocompleteParamsGET';
export * from './users/UsersGetNamesParamsGET';
38 changes: 38 additions & 0 deletions packages/rest-typings/src/v1/users/UsersGetNamesParamsGET.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Ajv from 'ajv';

const ajv = new Ajv({
coerceTypes: true,
});

export type UsersGetNamesParamsGET = { userIds: string[] } | { usernames: string[] };

const UsersGetNamesParamsGETSchema = {
oneOf: [
{
type: 'object',
properties: {
userIds: {
type: 'array',
items: {
type: 'string',
},
},
},
required: ['userIds'],
},
{
type: 'object',
properties: {
usernames: {
type: 'array',
items: {
type: 'string',
},
},
},
required: ['usernames'],
},
],
};

export const isUsersGetNamesParamsGETProps = ajv.compile<UsersGetNamesParamsGET>(UsersGetNamesParamsGETSchema);
Loading