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:
@ -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>
|
||||
|
||||
@ -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() ?? '',
|
||||
})),
|
||||
],
|
||||
]);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
|
||||
|
||||
export type EmailThreadMessage = {
|
||||
id: string;
|
||||
text: string;
|
||||
receivedAt: string;
|
||||
messageParticipants: EmailThreadMessageParticipant[];
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
|
||||
@ -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),
|
||||
)
|
||||
|
||||
@ -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);
|
||||
|
||||
Reference in New Issue
Block a user