From 3d5ecc9c083fd1c2a378f6ebf97d75e09c898862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:53:18 +0200 Subject: [PATCH] 7242 error when displaying message threads with a large number of participants (#7251) Closes #7242 --- .../emails/components/EmailThreadMessage.tsx | 9 ++-- ...ThreadMessagesOperationSignatureFactory.ts | 6 +-- .../components/IntermediaryMessages.tsx | 7 +-- .../components/RightDrawerEmailThread.tsx | 34 +++++--------- .../hooks/useRightDrawerEmailThread.ts | 44 ++++++++++++++++++- .../types/EmailThreadMessageParticipant.ts | 3 ++ .../types/EmailThreadMessageWithSender.ts | 6 +++ .../getDisplayNameFromParticipant.test.ts | 3 ++ 8 files changed, 77 insertions(+), 35 deletions(-) create mode 100644 packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageWithSender.ts diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessage.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessage.tsx index 32dde14ba..4f4a64b38 100644 --- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessage.tsx +++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessage.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react'; import styled from '@emotion/styled'; +import { useState } from 'react'; import { EmailThreadMessageBody } from '@/activities/emails/components/EmailThreadMessageBody'; import { EmailThreadMessageBodyPreview } from '@/activities/emails/components/EmailThreadMessageBodyPreview'; @@ -30,6 +30,7 @@ const StyledThreadMessageBody = styled.div` type EmailThreadMessageProps = { body: string; sentAt: string; + sender: EmailThreadMessageParticipant; participants: EmailThreadMessageParticipant[]; isExpanded?: boolean; }; @@ -37,17 +38,17 @@ type EmailThreadMessageProps = { export const EmailThreadMessage = ({ body, sentAt, + sender, participants, isExpanded = false, }: EmailThreadMessageProps) => { const [isOpen, setIsOpen] = useState(isExpanded); - const from = participants.find((participant) => participant.role === 'from'); const receivers = participants.filter( (participant) => participant.role !== 'from', ); - if (!from || receivers.length === 0) { + if (!sender || receivers.length === 0) { return null; } @@ -57,7 +58,7 @@ export const EmailThreadMessage = ({ style={{ cursor: isOpen ? 'auto' : 'pointer' }} > isOpen && setIsOpen(false)}> - + {isOpen && } diff --git a/packages/twenty-front/src/modules/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory.ts b/packages/twenty-front/src/modules/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory.ts index d141d867b..f818332fd 100644 --- a/packages/twenty-front/src/modules/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory.ts +++ b/packages/twenty-front/src/modules/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory.ts @@ -47,11 +47,7 @@ export const fetchAllThreadMessagesOperationSignatureFactory: RecordGqlOperation id: true, role: true, displayName: true, - participant: { - id: true, - email: true, - name: true, - }, + handle: true, person: true, workspaceMember: true, }, diff --git a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/IntermediaryMessages.tsx b/packages/twenty-front/src/modules/activities/emails/right-drawer/components/IntermediaryMessages.tsx index a830e8367..3ef96d573 100644 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/IntermediaryMessages.tsx +++ b/packages/twenty-front/src/modules/activities/emails/right-drawer/components/IntermediaryMessages.tsx @@ -1,9 +1,9 @@ -import { useState } from 'react'; import styled from '@emotion/styled'; +import { useState } from 'react'; import { IconArrowsVertical } from 'twenty-ui'; import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage'; -import { EmailThreadMessage as EmailThreadMessageType } from '@/activities/emails/types/EmailThreadMessage'; +import { EmailThreadMessageWithSender } from '@/activities/emails/types/EmailThreadMessageWithSender'; import { Button } from '@/ui/input/button/components/Button'; const StyledButtonContainer = styled.div` @@ -14,7 +14,7 @@ const StyledButtonContainer = styled.div` export const IntermediaryMessages = ({ messages, }: { - messages: EmailThreadMessageType[]; + messages: EmailThreadMessageWithSender[]; }) => { const [areMessagesOpen, setAreMessagesOpen] = useState(false); @@ -26,6 +26,7 @@ export const IntermediaryMessages = ({ messages.map((message) => ( { messageChannelLoading, } = useRightDrawerEmailThread(); - const visibleMessages = useMemo(() => { - return messages.filter(({ messageParticipants }) => { - const from = messageParticipants.find( - (participant) => participant.role === 'from', - ); - const receivers = messageParticipants.filter( - (participant) => participant.role !== 'from', - ); - return from && receivers.length > 0; - }); - }, [messages]); - useEffect(() => { - if (!visibleMessages[0]?.messageThread) { + if (!messages[0]?.messageThread) { return; } - setMessageThread(visibleMessages[0]?.messageThread); + setMessageThread(messages[0]?.messageThread); }); const { useRegisterClickOutsideListenerCallback } = useClickOutsideListener( @@ -93,17 +81,17 @@ export const RightDrawerEmailThread = () => { ), }); - const visibleMessagesCount = visibleMessages.length; - const is5OrMoreMessages = visibleMessagesCount >= 5; - const firstMessages = visibleMessages.slice( + const messagesCount = messages.length; + const is5OrMoreMessages = messagesCount >= 5; + const firstMessages = messages.slice( 0, - is5OrMoreMessages ? 2 : visibleMessagesCount - 1, + is5OrMoreMessages ? 2 : messagesCount - 1, ); const intermediaryMessages = is5OrMoreMessages - ? visibleMessages.slice(2, visibleMessagesCount - 1) + ? messages.slice(2, messagesCount - 1) : []; - const lastMessage = visibleMessages[visibleMessagesCount - 1]; - const subject = visibleMessages[0]?.subject; + const lastMessage = messages[messagesCount - 1]; + const subject = messages[0]?.subject; const canReply = useMemo(() => { return ( @@ -119,7 +107,7 @@ export const RightDrawerEmailThread = () => { const url = `https://mail.google.com/mail/?authuser=${connectedAccountHandle}#all/${messageThreadExternalId}`; window.open(url, '_blank'); }; - if (!thread) { + if (!thread || !messages.length) { return null; } return ( @@ -136,6 +124,7 @@ export const RightDrawerEmailThread = () => { {firstMessages.map((message) => ( { { const viewableRecordId = useRecoilValue(viewableRecordIdState); @@ -74,6 +77,30 @@ export const useRightDrawerEmailThread = () => { } }, [messages, isMessagesFetchComplete]); + // TODO: introduce nested filters so we can retrieve the message sender directly from the message query + const { records: messageSenders } = + useFindManyRecords({ + filter: { + messageId: { + in: messages.map(({ id }) => id), + }, + role: { + eq: 'from', + }, + }, + objectNameSingular: CoreObjectNameSingular.MessageParticipant, + recordGqlFields: { + id: true, + role: true, + displayName: true, + messageId: true, + handle: true, + person: true, + workspaceMember: true, + }, + skip: messages.length === 0, + }); + const { records: messageChannelMessageAssociationData } = useFindManyRecords({ filter: { @@ -123,9 +150,24 @@ export const useRightDrawerEmailThread = () => { const connectedAccountHandle = messageChannelData.length > 0 ? messageChannelData[0].handle : null; + const messagesWithSender: EmailThreadMessageWithSender[] = messages + .map((message) => { + const sender = messageSenders.find( + (messageSender) => messageSender.messageId === message.id, + ); + if (!sender) { + return null; + } + return { + ...message, + sender, + }; + }) + .filter(isDefined); + return { thread, - messages, + messages: messagesWithSender, messageThreadExternalId, connectedAccountHandle, threadLoading: messagesLoading, diff --git a/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageParticipant.ts b/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageParticipant.ts index ed81fa848..72dfa74df 100644 --- a/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageParticipant.ts +++ b/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageParticipant.ts @@ -3,9 +3,12 @@ import { Person } from '@/people/types/Person'; import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; export type EmailThreadMessageParticipant = { + id: string; displayName: string; handle: string; role: EmailParticipantRole; + messageId: string; person: Person; workspaceMember: WorkspaceMember; + __typename: 'EmailThreadMessageParticipant'; }; diff --git a/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageWithSender.ts b/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageWithSender.ts new file mode 100644 index 000000000..fe6916da2 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessageWithSender.ts @@ -0,0 +1,6 @@ +import { EmailThreadMessage } from '@/activities/emails/types/EmailThreadMessage'; +import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant'; + +export type EmailThreadMessageWithSender = EmailThreadMessage & { + sender: EmailThreadMessageParticipant; +}; diff --git a/packages/twenty-front/src/modules/activities/emails/utils/__tests__/getDisplayNameFromParticipant.test.ts b/packages/twenty-front/src/modules/activities/emails/utils/__tests__/getDisplayNameFromParticipant.test.ts index 7b4d8c7a8..28c971308 100644 --- a/packages/twenty-front/src/modules/activities/emails/utils/__tests__/getDisplayNameFromParticipant.test.ts +++ b/packages/twenty-front/src/modules/activities/emails/utils/__tests__/getDisplayNameFromParticipant.test.ts @@ -4,9 +4,12 @@ import { getDisplayNameFromParticipant } from '../getDisplayNameFromParticipant' describe('getDisplayNameFromParticipant', () => { const participantWithName: EmailThreadMessageParticipant = { + id: '2cac0ba7-0e60-46c6-86e7-e5b0bc55b7cf', + __typename: 'EmailThreadMessageParticipant', displayName: '', handle: '', role: 'from', + messageId: '638f52d1-fd55-4a2b-b0f3-9858ea3b2e91', person: { __typename: 'Person', id: '1',