7242 error when displaying message threads with a large number of participants (#7251)

Closes #7242
This commit is contained in:
Raphaël Bosi
2024-09-25 16:53:18 +02:00
committed by GitHub
parent 7669b40543
commit 3d5ecc9c08
8 changed files with 77 additions and 35 deletions

View File

@@ -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' }}
>
<StyledThreadMessageHeader onClick={() => isOpen && setIsOpen(false)}>
<EmailThreadMessageSender sender={from} sentAt={sentAt} />
<EmailThreadMessageSender sender={sender} sentAt={sentAt} />
{isOpen && <EmailThreadMessageReceivers receivers={receivers} />}
</StyledThreadMessageHeader>
<StyledThreadMessageBody>

View File

@@ -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,
},

View File

@@ -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) => (
<EmailThreadMessage
key={message.id}
sender={message.sender}
participants={message.messageParticipants}
body={message.text}
sentAt={message.receivedAt}

View File

@@ -55,23 +55,11 @@ export const RightDrawerEmailThread = () => {
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) => (
<EmailThreadMessage
key={message.id}
sender={message.sender}
participants={message.messageParticipants}
body={message.text}
sentAt={message.receivedAt}
@@ -144,6 +133,7 @@ export const RightDrawerEmailThread = () => {
<IntermediaryMessages messages={intermediaryMessages} />
<EmailThreadMessage
key={lastMessage.id}
sender={lastMessage.sender}
participants={lastMessage.messageParticipants}
body={lastMessage.text}
sentAt={lastMessage.receivedAt}

View File

@@ -6,6 +6,8 @@ import { EmailThread } from '@/activities/emails/types/EmailThread';
import { EmailThreadMessage } from '@/activities/emails/types/EmailThreadMessage';
import { MessageChannel } from '@/accounts/types/MessageChannel';
import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
import { EmailThreadMessageWithSender } from '@/activities/emails/types/EmailThreadMessageWithSender';
import { MessageChannelMessageAssociation } from '@/activities/emails/types/MessageChannelMessageAssociation';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
@@ -13,6 +15,7 @@ import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { isDefined } from 'twenty-ui';
export const useRightDrawerEmailThread = () => {
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<EmailThreadMessageParticipant>({
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<MessageChannelMessageAssociation>({
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,

View File

@@ -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';
};

View File

@@ -0,0 +1,6 @@
import { EmailThreadMessage } from '@/activities/emails/types/EmailThreadMessage';
import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
export type EmailThreadMessageWithSender = EmailThreadMessage & {
sender: EmailThreadMessageParticipant;
};

View File

@@ -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',