mirror of
https://github.com/lingble/twenty.git
synced 2025-10-29 20:02:29 +00:00
7242 error when displaying message threads with a large number of participants (#7251)
Closes #7242
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
};
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { EmailThreadMessage } from '@/activities/emails/types/EmailThreadMessage';
|
||||
import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
|
||||
|
||||
export type EmailThreadMessageWithSender = EmailThreadMessage & {
|
||||
sender: EmailThreadMessageParticipant;
|
||||
};
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user