Fetch messages with hard coded thread id (#3613)

* Fetch messages with hard coded thread id

* Fix test

* Use first workspace member or person names

---------

Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
Thomas Trompette
2024-01-25 11:13:32 +01:00
committed by GitHub
parent 46f0eb522f
commit 7845e04f6b
8 changed files with 148 additions and 105 deletions

View File

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { useState } from 'react';
import styled from '@emotion/styled';
import { EmailThreadMessageBody } from '@/activities/emails/components/EmailThreadMessageBody';
import { EmailThreadMessageBodyPreview } from '@/activities/emails/components/EmailThreadMessageBodyPreview';
import { EmailThreadMessageSender } from '@/activities/emails/components/EmailThreadMessageSender';
import { MockedEmailUser } from '@/activities/emails/mocks/mockedEmailThreads';
import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
const StyledThreadMessage = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
@ -22,21 +22,52 @@ const StyledThreadMessageHeader = styled.div`
`;
type EmailThreadMessageProps = {
id: string;
body: string;
sentAt: string;
from: MockedEmailUser;
to: MockedEmailUser[];
participants: EmailThreadMessageParticipant[];
};
const getDisplayNameFromParticipant = (
participant: EmailThreadMessageParticipant,
) => {
if (participant.person) {
return `${participant.person?.name?.firstName} ${participant.person?.name?.lastName}`;
}
if (participant.workspaceMember) {
return `${participant.workspaceMember?.name?.firstName} ${participant.workspaceMember?.name?.lastName}`;
}
if (participant.displayName) {
return participant.displayName;
}
if (participant.handle) {
return participant.handle;
}
return 'Unknown';
};
export const EmailThreadMessage = ({
body,
sentAt,
from,
participants,
}: EmailThreadMessageProps) => {
const { displayName, avatarUrl } = from;
const [isOpen, setIsOpen] = useState(false);
const from = participants.find((participant) => participant.role === 'from');
const to = participants.filter((participant) => participant.role === 'to');
if (!from || to.length === 0) {
return null;
}
const displayName = getDisplayNameFromParticipant(from);
const avatarUrl =
from.person?.avatarUrl ?? from.workspaceMember?.avatarUrl ?? '';
return (
<StyledThreadMessage onClick={() => setIsOpen(!isOpen)}>
<StyledThreadMessageHeader>

View File

@ -1,31 +1,13 @@
import { DateTime } from 'luxon';
import { Scalars, TimelineThread } from '~/generated/graphql';
export type MockedThread = {
id: string;
} & TimelineThread;
export type MockedEmailUser = {
avatarUrl: string;
displayName: string;
workspaceMemberId?: string;
personId?: string;
};
export type MockedMessage = {
id: string;
from: MockedEmailUser;
to: MockedEmailUser[];
subject: string;
body: string;
sentAt: string;
};
export const mockedEmailThreads: MockedThread[] = [
{
__typename: 'TimelineThread',
id: '1',
id: '4e88ec1f-a386-4235-bd82-98f25f6d557e',
body: 'This is a test email' as Scalars['String'],
numberOfMessagesInThread: 5 as Scalars['Float'],
read: true as Scalars['Boolean'],
@ -36,7 +18,7 @@ export const mockedEmailThreads: MockedThread[] = [
},
{
__typename: 'TimelineThread',
id: '2',
id: '4e88ec1f-a386-4235-bd82-98f25f6d557e',
body: 'This is a second test email' as Scalars['String'],
numberOfMessagesInThread: 5 as Scalars['Float'],
read: true as Scalars['Boolean'],
@ -46,65 +28,3 @@ export const mockedEmailThreads: MockedThread[] = [
subject: 'Test email number 2' as Scalars['String'],
},
];
export const mockedMessagesByThread: Map<string, MockedMessage[]> = new Map([
[
'1',
Array.from({ length: 5 }).map((_, i) => ({
id: `id${i + 1}`,
from: {
avatarUrl: '',
displayName: `User ${i + 1}`,
workspaceMemberId: `workspaceMemberId${i + 1}`,
personId: `personId${i + 1}`,
},
to: [
{
avatarUrl: 'https://favicon.twenty.com/qonto.com',
displayName: `User ${i + 2}`,
workspaceMemberId: `workspaceMemberId${i + 1}`,
personId: `personId${i + 2}`,
},
],
subject: `Subject ${i + 1}`,
body: `Body ${
i + 1
}. I am testing a very long body. I am adding more text.
I also want to test a new line. To see if it works.
I am adding a new paragraph.
Thomas`,
sentAt: DateTime.fromFormat('2021-03-12', 'yyyy-MM-dd').toISO() ?? '',
})),
],
[
'2',
Array.from({ length: 5 }).map((_, i) => ({
id: `id${i + 10}`,
from: {
avatarUrl: '',
displayName: `Other user ${i + 1}`,
workspaceMemberId: `workspaceMemberId${i + 1}`,
personId: `personId${i + 1}`,
},
to: [
{
avatarUrl: 'https://favicon.twenty.com/qonto.com',
displayName: `Other user ${i + 2}`,
workspaceMemberId: `workspaceMemberId${i + 1}`,
personId: `personId${i + 2}`,
},
],
subject: `Subject ${i + 1}`,
body: `Body ${
i + 1
}. Hello, I am testing a very long body. I am adding more text.
I am adding a new paragraph.
Thomas`,
sentAt: DateTime.fromFormat('2021-03-12', 'yyyy-MM-dd').toISO() ?? '',
})),
],
]);

View File

@ -4,8 +4,10 @@ import { useRecoilValue } from 'recoil';
import { EmailThreadHeader } from '@/activities/emails/components/EmailThreadHeader';
import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage';
import { mockedMessagesByThread } from '@/activities/emails/mocks/mockedEmailThreads';
import { viewableEmailThreadState } from '@/activities/emails/state/viewableEmailThreadState';
import { EmailThreadMessage as EmailThreadMessageType } from '@/activities/emails/types/EmailThreadMessage';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
const StyledContainer = styled.div`
box-sizing: border-box;
@ -20,27 +22,37 @@ const StyledContainer = styled.div`
export const RightDrawerEmailThread = () => {
const viewableEmailThread = useRecoilValue(viewableEmailThreadState);
const { records: messages } = useFindManyRecords<EmailThreadMessageType>({
depth: 3,
filter: {
messageThreadId: {
eq: viewableEmailThread?.id,
},
},
objectNameSingular: CoreObjectNameSingular.Message,
orderBy: {
receivedAt: 'DescNullsLast',
},
skip: !viewableEmailThread,
useRecordsWithoutConnection: true,
});
if (!viewableEmailThread) {
return null;
}
const mockedMessages =
mockedMessagesByThread.get(viewableEmailThread.id) ?? [];
return (
<StyledContainer>
<EmailThreadHeader
subject={viewableEmailThread.subject}
lastMessageSentAt={viewableEmailThread.receivedAt}
/>
{mockedMessages.map((message) => (
{messages.map((message) => (
<EmailThreadMessage
key={message.id}
id={message.id}
from={message.from}
to={message.to}
body={message.body}
sentAt={message.sentAt}
participants={message.messageParticipants}
body={message.text}
sentAt={message.receivedAt}
/>
))}
</StyledContainer>

View File

@ -0,0 +1,8 @@
import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
export type EmailThreadMessage = {
id: string;
text: string;
receivedAt: string;
messageParticipants: EmailThreadMessageParticipant[];
};

View File

@ -0,0 +1,10 @@
import { Person } from '@/people/types/Person';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
export type EmailThreadMessageParticipant = {
displayName: string;
handle: string;
role: string;
person: Person;
workspaceMember: WorkspaceMember;
};

View File

@ -27,12 +27,32 @@ const getOneToManyRelation = () => {
label
url
}
accountOwner
{
id
}
linkedinLink
{
label
url
}
attachments
{
edges {
node {
id
}
}
}
domainName
opportunities
{
edges {
node {
id
}
}
}
annualRecurringRevenue
{
amountMicros
@ -41,6 +61,30 @@ domainName
createdAt
address
updatedAt
activityTargets
{
edges {
node {
id
}
}
}
favorites
{
edges {
node {
id
}
}
}
people
{
edges {
node {
id
}
}
}
name
accountOwnerId
employees
@ -89,9 +133,17 @@ const getOneToManyFromRelationField = () => {
personId
pointOfContactId
updatedAt
company
{
id
}
companyId
pipelineStepId
probability
pipelineStep
{
id
}
closeDate
amount
{
@ -100,6 +152,14 @@ closeDate
}
id
createdAt
pointOfContact
{
id
}
person
{
id
}
}
}
}`,

View File

@ -48,7 +48,6 @@ export const useMapFieldMetadataToGraphQLQuery = () => {
{
id
${(relationMetadataItem?.fields ?? [])
.filter((field) => field.type !== 'RELATION')
.map((field) =>
mapFieldMetadataToGraphQLQuery(field, maxDepthForRelations - 1),
)
@ -68,7 +67,6 @@ export const useMapFieldMetadataToGraphQLQuery = () => {
{
id
${(relationMetadataItem?.fields ?? [])
.filter((field) => field.type !== 'RELATION')
.map((field) =>
mapFieldMetadataToGraphQLQuery(field, maxDepthForRelations - 1),
)
@ -90,7 +88,6 @@ export const useMapFieldMetadataToGraphQLQuery = () => {
node {
id
${(relationMetadataItem?.fields ?? [])
.filter((field) => field.type !== 'RELATION')
.map((field) =>
mapFieldMetadataToGraphQLQuery(field, maxDepthForRelations - 1),
)

View File

@ -32,11 +32,13 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
onCompleted,
skip,
useRecordsWithoutConnection = false,
depth,
}: ObjectMetadataItemIdentifier &
ObjectRecordQueryVariables & {
onCompleted?: (data: ObjectRecordConnection<T>) => void;
skip?: boolean;
useRecordsWithoutConnection?: boolean;
depth?: number;
}) => {
const findManyQueryStateIdentifier =
objectNameSingular +
@ -56,9 +58,12 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
isFetchingMoreRecordsFamilyState(findManyQueryStateIdentifier),
);
const { objectMetadataItem, findManyRecordsQuery } = useObjectMetadataItem({
objectNameSingular,
});
const { objectMetadataItem, findManyRecordsQuery } = useObjectMetadataItem(
{
objectNameSingular,
},
depth,
);
const { enqueueSnackBar } = useSnackBar();
const currentWorkspace = useRecoilValue(currentWorkspaceState);