3263 modify timeline messagingservice to allow the frontend to get multiple participants in a thread (#3611)
* wip * wip * add pagination * wip * wip * wip * update resolver * wip * wip * endpoint is working but there is still work to do * merge main * wip * subject is now first subject * number of messages is working * improving query * fix bug * fix bug * added parameter * pagination introduced a bug * pagination is working * fix type * improve typing * improve typing * fix bug * add displayName * display displayName in the frontend * move entities * fix * generate metadata * add avatarUrl * modify after comments on PR * updates * remove email mocks * remove console log * move files * remove mock * use constant * use constant * use fragments * remove console.log * generate * changes made * update DTO * generate
This commit is contained in:
@ -443,6 +443,17 @@ export type Telemetry = {
|
|||||||
enabled: Scalars['Boolean']['output'];
|
enabled: Scalars['Boolean']['output'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TimelineThreadParticipant = {
|
||||||
|
__typename?: 'TimelineThreadParticipant';
|
||||||
|
avatarUrl: Scalars['String']['output'];
|
||||||
|
displayName: Scalars['String']['output'];
|
||||||
|
firstName: Scalars['String']['output'];
|
||||||
|
handle: Scalars['String']['output'];
|
||||||
|
lastName: Scalars['String']['output'];
|
||||||
|
personId?: Maybe<Scalars['String']['output']>;
|
||||||
|
workspaceMemberId?: Maybe<Scalars['String']['output']>;
|
||||||
|
};
|
||||||
|
|
||||||
export type UpdateFieldInput = {
|
export type UpdateFieldInput = {
|
||||||
defaultValue?: InputMaybe<Scalars['JSON']['input']>;
|
defaultValue?: InputMaybe<Scalars['JSON']['input']>;
|
||||||
description?: InputMaybe<Scalars['String']['input']>;
|
description?: InputMaybe<Scalars['String']['input']>;
|
||||||
|
|||||||
@ -402,12 +402,16 @@ export type QueryFindWorkspaceFromInviteHashArgs = {
|
|||||||
|
|
||||||
|
|
||||||
export type QueryGetTimelineThreadsFromCompanyIdArgs = {
|
export type QueryGetTimelineThreadsFromCompanyIdArgs = {
|
||||||
companyId: Scalars['String'];
|
companyId: Scalars['ID'];
|
||||||
|
page: Scalars['Int'];
|
||||||
|
pageSize: Scalars['Int'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type QueryGetTimelineThreadsFromPersonIdArgs = {
|
export type QueryGetTimelineThreadsFromPersonIdArgs = {
|
||||||
personId: Scalars['String'];
|
page: Scalars['Int'];
|
||||||
|
pageSize: Scalars['Int'];
|
||||||
|
personId: Scalars['ID'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -491,15 +495,28 @@ export type Telemetry = {
|
|||||||
|
|
||||||
export type TimelineThread = {
|
export type TimelineThread = {
|
||||||
__typename?: 'TimelineThread';
|
__typename?: 'TimelineThread';
|
||||||
body: Scalars['String'];
|
firstParticipant: TimelineThreadParticipant;
|
||||||
|
id: Scalars['ID'];
|
||||||
|
lastMessageBody: Scalars['String'];
|
||||||
|
lastMessageReceivedAt: Scalars['DateTime'];
|
||||||
|
lastTwoParticipants: Array<TimelineThreadParticipant>;
|
||||||
numberOfMessagesInThread: Scalars['Float'];
|
numberOfMessagesInThread: Scalars['Float'];
|
||||||
|
participantCount: Scalars['Float'];
|
||||||
read: Scalars['Boolean'];
|
read: Scalars['Boolean'];
|
||||||
receivedAt: Scalars['DateTime'];
|
|
||||||
senderName: Scalars['String'];
|
|
||||||
senderPictureUrl: Scalars['String'];
|
|
||||||
subject: Scalars['String'];
|
subject: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TimelineThreadParticipant = {
|
||||||
|
__typename?: 'TimelineThreadParticipant';
|
||||||
|
avatarUrl: Scalars['String'];
|
||||||
|
displayName: Scalars['String'];
|
||||||
|
firstName: Scalars['String'];
|
||||||
|
handle: Scalars['String'];
|
||||||
|
lastName: Scalars['String'];
|
||||||
|
personId?: Maybe<Scalars['ID']>;
|
||||||
|
workspaceMemberId?: Maybe<Scalars['ID']>;
|
||||||
|
};
|
||||||
|
|
||||||
export type TransientToken = {
|
export type TransientToken = {
|
||||||
__typename?: 'TransientToken';
|
__typename?: 'TransientToken';
|
||||||
transientToken: AuthToken;
|
transientToken: AuthToken;
|
||||||
@ -694,19 +711,27 @@ export type RelationEdge = {
|
|||||||
node: Relation;
|
node: Relation;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ParticipantFragmentFragment = { __typename?: 'TimelineThreadParticipant', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string };
|
||||||
|
|
||||||
|
export type TimelineThreadFragmentFragment = { __typename?: 'TimelineThread', id: string, read: boolean, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> };
|
||||||
|
|
||||||
export type GetTimelineThreadsFromCompanyIdQueryVariables = Exact<{
|
export type GetTimelineThreadsFromCompanyIdQueryVariables = Exact<{
|
||||||
companyId: Scalars['String'];
|
companyId: Scalars['ID'];
|
||||||
|
page: Scalars['Int'];
|
||||||
|
pageSize: Scalars['Int'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type GetTimelineThreadsFromCompanyIdQuery = { __typename?: 'Query', getTimelineThreadsFromCompanyId: Array<{ __typename?: 'TimelineThread', body: string, numberOfMessagesInThread: number, read: boolean, receivedAt: string, senderName: string, senderPictureUrl: string, subject: string }> };
|
export type GetTimelineThreadsFromCompanyIdQuery = { __typename?: 'Query', getTimelineThreadsFromCompanyId: Array<{ __typename?: 'TimelineThread', id: string, read: boolean, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> };
|
||||||
|
|
||||||
export type GetTimelineThreadsFromPersonIdQueryVariables = Exact<{
|
export type GetTimelineThreadsFromPersonIdQueryVariables = Exact<{
|
||||||
personId: Scalars['String'];
|
personId: Scalars['ID'];
|
||||||
|
page: Scalars['Int'];
|
||||||
|
pageSize: Scalars['Int'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type GetTimelineThreadsFromPersonIdQuery = { __typename?: 'Query', getTimelineThreadsFromPersonId: Array<{ __typename?: 'TimelineThread', body: string, numberOfMessagesInThread: number, read: boolean, receivedAt: string, senderName: string, senderPictureUrl: string, subject: string }> };
|
export type GetTimelineThreadsFromPersonIdQuery = { __typename?: 'Query', getTimelineThreadsFromPersonId: Array<{ __typename?: 'TimelineThread', id: string, read: boolean, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: string | null, workspaceMemberId?: string | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> };
|
||||||
|
|
||||||
export type CreateEventMutationVariables = Exact<{
|
export type CreateEventMutationVariables = Exact<{
|
||||||
type: Scalars['String'];
|
type: Scalars['String'];
|
||||||
@ -859,6 +884,34 @@ export type GetWorkspaceFromInviteHashQueryVariables = Exact<{
|
|||||||
|
|
||||||
export type GetWorkspaceFromInviteHashQuery = { __typename?: 'Query', findWorkspaceFromInviteHash: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, allowImpersonation: boolean } };
|
export type GetWorkspaceFromInviteHashQuery = { __typename?: 'Query', findWorkspaceFromInviteHash: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, allowImpersonation: boolean } };
|
||||||
|
|
||||||
|
export const ParticipantFragmentFragmentDoc = gql`
|
||||||
|
fragment ParticipantFragment on TimelineThreadParticipant {
|
||||||
|
personId
|
||||||
|
workspaceMemberId
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
displayName
|
||||||
|
avatarUrl
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export const TimelineThreadFragmentFragmentDoc = gql`
|
||||||
|
fragment TimelineThreadFragment on TimelineThread {
|
||||||
|
id
|
||||||
|
read
|
||||||
|
firstParticipant {
|
||||||
|
...ParticipantFragment
|
||||||
|
}
|
||||||
|
lastTwoParticipants {
|
||||||
|
...ParticipantFragment
|
||||||
|
}
|
||||||
|
lastMessageReceivedAt
|
||||||
|
lastMessageBody
|
||||||
|
subject
|
||||||
|
numberOfMessagesInThread
|
||||||
|
participantCount
|
||||||
|
}
|
||||||
|
${ParticipantFragmentFragmentDoc}`;
|
||||||
export const AuthTokenFragmentFragmentDoc = gql`
|
export const AuthTokenFragmentFragmentDoc = gql`
|
||||||
fragment AuthTokenFragment on AuthToken {
|
fragment AuthTokenFragment on AuthToken {
|
||||||
token
|
token
|
||||||
@ -911,18 +964,16 @@ export const UserQueryFragmentFragmentDoc = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const GetTimelineThreadsFromCompanyIdDocument = gql`
|
export const GetTimelineThreadsFromCompanyIdDocument = gql`
|
||||||
query GetTimelineThreadsFromCompanyId($companyId: String!) {
|
query GetTimelineThreadsFromCompanyId($companyId: ID!, $page: Int!, $pageSize: Int!) {
|
||||||
getTimelineThreadsFromCompanyId(companyId: $companyId) {
|
getTimelineThreadsFromCompanyId(
|
||||||
body
|
companyId: $companyId
|
||||||
numberOfMessagesInThread
|
page: $page
|
||||||
read
|
pageSize: $pageSize
|
||||||
receivedAt
|
) {
|
||||||
senderName
|
...TimelineThreadFragment
|
||||||
senderPictureUrl
|
|
||||||
subject
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
${TimelineThreadFragmentFragmentDoc}`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __useGetTimelineThreadsFromCompanyIdQuery__
|
* __useGetTimelineThreadsFromCompanyIdQuery__
|
||||||
@ -937,6 +988,8 @@ export const GetTimelineThreadsFromCompanyIdDocument = gql`
|
|||||||
* const { data, loading, error } = useGetTimelineThreadsFromCompanyIdQuery({
|
* const { data, loading, error } = useGetTimelineThreadsFromCompanyIdQuery({
|
||||||
* variables: {
|
* variables: {
|
||||||
* companyId: // value for 'companyId'
|
* companyId: // value for 'companyId'
|
||||||
|
* page: // value for 'page'
|
||||||
|
* pageSize: // value for 'pageSize'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
@ -952,18 +1005,16 @@ export type GetTimelineThreadsFromCompanyIdQueryHookResult = ReturnType<typeof u
|
|||||||
export type GetTimelineThreadsFromCompanyIdLazyQueryHookResult = ReturnType<typeof useGetTimelineThreadsFromCompanyIdLazyQuery>;
|
export type GetTimelineThreadsFromCompanyIdLazyQueryHookResult = ReturnType<typeof useGetTimelineThreadsFromCompanyIdLazyQuery>;
|
||||||
export type GetTimelineThreadsFromCompanyIdQueryResult = Apollo.QueryResult<GetTimelineThreadsFromCompanyIdQuery, GetTimelineThreadsFromCompanyIdQueryVariables>;
|
export type GetTimelineThreadsFromCompanyIdQueryResult = Apollo.QueryResult<GetTimelineThreadsFromCompanyIdQuery, GetTimelineThreadsFromCompanyIdQueryVariables>;
|
||||||
export const GetTimelineThreadsFromPersonIdDocument = gql`
|
export const GetTimelineThreadsFromPersonIdDocument = gql`
|
||||||
query GetTimelineThreadsFromPersonId($personId: String!) {
|
query GetTimelineThreadsFromPersonId($personId: ID!, $page: Int!, $pageSize: Int!) {
|
||||||
getTimelineThreadsFromPersonId(personId: $personId) {
|
getTimelineThreadsFromPersonId(
|
||||||
body
|
personId: $personId
|
||||||
numberOfMessagesInThread
|
page: $page
|
||||||
read
|
pageSize: $pageSize
|
||||||
receivedAt
|
) {
|
||||||
senderName
|
...TimelineThreadFragment
|
||||||
senderPictureUrl
|
|
||||||
subject
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
${TimelineThreadFragmentFragmentDoc}`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __useGetTimelineThreadsFromPersonIdQuery__
|
* __useGetTimelineThreadsFromPersonIdQuery__
|
||||||
@ -978,6 +1029,8 @@ export const GetTimelineThreadsFromPersonIdDocument = gql`
|
|||||||
* const { data, loading, error } = useGetTimelineThreadsFromPersonIdQuery({
|
* const { data, loading, error } = useGetTimelineThreadsFromPersonIdQuery({
|
||||||
* variables: {
|
* variables: {
|
||||||
* personId: // value for 'personId'
|
* personId: // value for 'personId'
|
||||||
|
* page: // value for 'page'
|
||||||
|
* pageSize: // value for 'pageSize'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -92,20 +92,22 @@ export const EmailThreadPreview = ({
|
|||||||
<StyledCardContent onClick={() => onClick()} divider={divider}>
|
<StyledCardContent onClick={() => onClick()} divider={divider}>
|
||||||
<StyledHeading unread={!thread.read}>
|
<StyledHeading unread={!thread.read}>
|
||||||
<StyledAvatar
|
<StyledAvatar
|
||||||
avatarUrl={thread.senderPictureUrl}
|
avatarUrl={thread.firstParticipant.avatarUrl}
|
||||||
placeholder={thread.senderName}
|
placeholder={thread.firstParticipant.displayName}
|
||||||
type="rounded"
|
type="rounded"
|
||||||
/>
|
/>
|
||||||
<StyledSenderName>{thread.senderName}</StyledSenderName>
|
<StyledSenderName>
|
||||||
|
{thread.firstParticipant.displayName}
|
||||||
|
</StyledSenderName>
|
||||||
<StyledThreadCount>{thread.numberOfMessagesInThread}</StyledThreadCount>
|
<StyledThreadCount>{thread.numberOfMessagesInThread}</StyledThreadCount>
|
||||||
</StyledHeading>
|
</StyledHeading>
|
||||||
|
|
||||||
<StyledSubjectAndBody>
|
<StyledSubjectAndBody>
|
||||||
<StyledSubject unread={!thread.read}>{thread.subject}</StyledSubject>
|
<StyledSubject unread={!thread.read}>{thread.subject}</StyledSubject>
|
||||||
<StyledBody>{thread.body}</StyledBody>
|
<StyledBody>{thread.lastMessageBody}</StyledBody>
|
||||||
</StyledSubjectAndBody>
|
</StyledSubjectAndBody>
|
||||||
<StyledReceivedAt>
|
<StyledReceivedAt>
|
||||||
{formatToHumanReadableDate(thread.receivedAt)}
|
{formatToHumanReadableDate(thread.lastMessageReceivedAt)}
|
||||||
</StyledReceivedAt>
|
</StyledReceivedAt>
|
||||||
</StyledCardContent>
|
</StyledCardContent>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,11 +2,8 @@ import { useQuery } from '@apollo/client';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview';
|
import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview';
|
||||||
|
import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from '@/activities/emails/constants/messaging.constants';
|
||||||
import { useEmailThread } from '@/activities/emails/hooks/useEmailThread';
|
import { useEmailThread } from '@/activities/emails/hooks/useEmailThread';
|
||||||
import {
|
|
||||||
mockedEmailThreads,
|
|
||||||
MockedThread,
|
|
||||||
} from '@/activities/emails/mocks/mockedEmailThreads';
|
|
||||||
import { getTimelineThreadsFromCompanyId } from '@/activities/emails/queries/getTimelineThreadsFromCompanyId';
|
import { getTimelineThreadsFromCompanyId } from '@/activities/emails/queries/getTimelineThreadsFromCompanyId';
|
||||||
import { getTimelineThreadsFromPersonId } from '@/activities/emails/queries/getTimelineThreadsFromPersonId';
|
import { getTimelineThreadsFromPersonId } from '@/activities/emails/queries/getTimelineThreadsFromPersonId';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
@ -17,6 +14,7 @@ import {
|
|||||||
} from '@/ui/display/typography/components/H1Title';
|
} from '@/ui/display/typography/components/H1Title';
|
||||||
import { Card } from '@/ui/layout/card/components/Card';
|
import { Card } from '@/ui/layout/card/components/Card';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
|
import { TimelineThread } from '~/generated/graphql';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -46,10 +44,13 @@ export const EmailThreads = ({
|
|||||||
? getTimelineThreadsFromPersonId
|
? getTimelineThreadsFromPersonId
|
||||||
: getTimelineThreadsFromCompanyId;
|
: getTimelineThreadsFromCompanyId;
|
||||||
|
|
||||||
const threadQueryVariables =
|
const threadQueryVariables = {
|
||||||
entity.targetObjectNameSingular === CoreObjectNameSingular.Person
|
...(entity.targetObjectNameSingular === CoreObjectNameSingular.Person
|
||||||
? { personId: entity.id }
|
? { personId: entity.id }
|
||||||
: { companyId: entity.id };
|
: { companyId: entity.id }),
|
||||||
|
page: 1,
|
||||||
|
pageSize: TIMELINE_THREADS_DEFAULT_PAGE_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
const threads = useQuery(threadQuery, {
|
const threads = useQuery(threadQuery, {
|
||||||
variables: threadQueryVariables,
|
variables: threadQueryVariables,
|
||||||
@ -59,16 +60,12 @@ export const EmailThreads = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// To use once the id is returned by the query
|
const timelineThreads: TimelineThread[] =
|
||||||
|
threads.data[
|
||||||
// const fetchedTimelineThreads: TimelineThread[] =
|
entity.targetObjectNameSingular === CoreObjectNameSingular.Person
|
||||||
// threads.data[
|
? 'getTimelineThreadsFromPersonId'
|
||||||
// entity.targetObjectNameSingular === CoreObjectNameSingular.Person
|
: 'getTimelineThreadsFromCompanyId'
|
||||||
// ? 'getTimelineThreadsFromPersonId'
|
];
|
||||||
// : 'getTimelineThreadsFromCompanyId'
|
|
||||||
// ];
|
|
||||||
|
|
||||||
const timelineThreads = mockedEmailThreads;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
@ -77,16 +74,14 @@ export const EmailThreads = ({
|
|||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
Inbox{' '}
|
Inbox{' '}
|
||||||
<StyledEmailCount>
|
<StyledEmailCount>{timelineThreads?.length}</StyledEmailCount>
|
||||||
{timelineThreads && timelineThreads.length}
|
|
||||||
</StyledEmailCount>
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
fontColor={H1TitleFontColor.Primary}
|
fontColor={H1TitleFontColor.Primary}
|
||||||
/>
|
/>
|
||||||
<Card>
|
<Card>
|
||||||
{timelineThreads &&
|
{timelineThreads &&
|
||||||
timelineThreads.map((thread: MockedThread, index: number) => (
|
timelineThreads.map((thread: TimelineThread, index: number) => (
|
||||||
<EmailThreadPreview
|
<EmailThreadPreview
|
||||||
key={index}
|
key={index}
|
||||||
divider={index < timelineThreads.length - 1}
|
divider={index < timelineThreads.length - 1}
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export const TIMELINE_THREADS_DEFAULT_PAGE_SIZE = 20;
|
||||||
@ -1,15 +1,15 @@
|
|||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { MockedThread } from '@/activities/emails/mocks/mockedEmailThreads';
|
|
||||||
import { useOpenEmailThreadRightDrawer } from '@/activities/emails/right-drawer/hooks/useOpenEmailThreadRightDrawer';
|
import { useOpenEmailThreadRightDrawer } from '@/activities/emails/right-drawer/hooks/useOpenEmailThreadRightDrawer';
|
||||||
import { viewableEmailThreadState } from '@/activities/emails/state/viewableEmailThreadState';
|
import { viewableEmailThreadState } from '@/activities/emails/state/viewableEmailThreadState';
|
||||||
|
import { TimelineThread } from '~/generated/graphql';
|
||||||
|
|
||||||
export const useEmailThread = () => {
|
export const useEmailThread = () => {
|
||||||
const [, setViewableEmailThread] = useRecoilState(viewableEmailThreadState);
|
const [, setViewableEmailThread] = useRecoilState(viewableEmailThreadState);
|
||||||
|
|
||||||
const openEmailThredRightDrawer = useOpenEmailThreadRightDrawer();
|
const openEmailThredRightDrawer = useOpenEmailThreadRightDrawer();
|
||||||
|
|
||||||
const openEmailThread = (thread: MockedThread) => {
|
const openEmailThread = (thread: TimelineThread) => {
|
||||||
openEmailThredRightDrawer();
|
openEmailThredRightDrawer();
|
||||||
|
|
||||||
setViewableEmailThread(thread);
|
setViewableEmailThread(thread);
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
import { Scalars, TimelineThread } from '~/generated/graphql';
|
|
||||||
|
|
||||||
export type MockedThread = {
|
|
||||||
id: string;
|
|
||||||
} & TimelineThread;
|
|
||||||
|
|
||||||
export const mockedEmailThreads: MockedThread[] = [
|
|
||||||
{
|
|
||||||
__typename: 'TimelineThread',
|
|
||||||
id: 'ec7e12b9-4063-410f-ae9a-30e32452b9c0',
|
|
||||||
body: 'This is a test email' as Scalars['String'],
|
|
||||||
numberOfMessagesInThread: 5 as Scalars['Float'],
|
|
||||||
read: true as Scalars['Boolean'],
|
|
||||||
receivedAt: new Date().toISOString() as Scalars['DateTime'],
|
|
||||||
senderName: 'Thom Trp' as Scalars['String'],
|
|
||||||
senderPictureUrl: '' as Scalars['String'],
|
|
||||||
subject: 'Test email' as Scalars['String'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__typename: 'TimelineThread',
|
|
||||||
id: 'ec7e12b9-4063-410f-ae9a-30e32452b9c0',
|
|
||||||
body: 'This is a second test email' as Scalars['String'],
|
|
||||||
numberOfMessagesInThread: 5 as Scalars['Float'],
|
|
||||||
read: true as Scalars['Boolean'],
|
|
||||||
receivedAt: new Date().toISOString() as Scalars['DateTime'],
|
|
||||||
senderName: 'Coco Den' as Scalars['String'],
|
|
||||||
senderPictureUrl: '' as Scalars['String'],
|
|
||||||
subject: 'Test email number 2' as Scalars['String'],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const participantFragment = gql`
|
||||||
|
fragment ParticipantFragment on TimelineThreadParticipant {
|
||||||
|
personId
|
||||||
|
workspaceMemberId
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
displayName
|
||||||
|
avatarUrl
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
import { participantFragment } from '@/activities/emails/queries/fragments/participantFragment';
|
||||||
|
|
||||||
|
export const timelineThreadFragment = gql`
|
||||||
|
fragment TimelineThreadFragment on TimelineThread {
|
||||||
|
id
|
||||||
|
read
|
||||||
|
firstParticipant {
|
||||||
|
...ParticipantFragment
|
||||||
|
}
|
||||||
|
lastTwoParticipants {
|
||||||
|
...ParticipantFragment
|
||||||
|
}
|
||||||
|
lastMessageReceivedAt
|
||||||
|
lastMessageBody
|
||||||
|
subject
|
||||||
|
numberOfMessagesInThread
|
||||||
|
participantCount
|
||||||
|
}
|
||||||
|
${participantFragment}
|
||||||
|
`;
|
||||||
@ -1,15 +1,20 @@
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
import { timelineThreadFragment } from '@/activities/emails/queries/fragments/timelineThreadFragment';
|
||||||
|
|
||||||
export const getTimelineThreadsFromCompanyId = gql`
|
export const getTimelineThreadsFromCompanyId = gql`
|
||||||
query GetTimelineThreadsFromCompanyId($companyId: String!) {
|
query GetTimelineThreadsFromCompanyId(
|
||||||
getTimelineThreadsFromCompanyId(companyId: $companyId) {
|
$companyId: ID!
|
||||||
body
|
$page: Int!
|
||||||
numberOfMessagesInThread
|
$pageSize: Int!
|
||||||
read
|
) {
|
||||||
receivedAt
|
getTimelineThreadsFromCompanyId(
|
||||||
senderName
|
companyId: $companyId
|
||||||
senderPictureUrl
|
page: $page
|
||||||
subject
|
pageSize: $pageSize
|
||||||
|
) {
|
||||||
|
...TimelineThreadFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
${timelineThreadFragment}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -1,15 +1,20 @@
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
import { timelineThreadFragment } from '@/activities/emails/queries/fragments/timelineThreadFragment';
|
||||||
|
|
||||||
export const getTimelineThreadsFromPersonId = gql`
|
export const getTimelineThreadsFromPersonId = gql`
|
||||||
query GetTimelineThreadsFromPersonId($personId: String!) {
|
query GetTimelineThreadsFromPersonId(
|
||||||
getTimelineThreadsFromPersonId(personId: $personId) {
|
$personId: ID!
|
||||||
body
|
$page: Int!
|
||||||
numberOfMessagesInThread
|
$pageSize: Int!
|
||||||
read
|
) {
|
||||||
receivedAt
|
getTimelineThreadsFromPersonId(
|
||||||
senderName
|
personId: $personId
|
||||||
senderPictureUrl
|
page: $page
|
||||||
subject
|
pageSize: $pageSize
|
||||||
|
) {
|
||||||
|
...TimelineThreadFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
${timelineThreadFragment}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -50,7 +50,7 @@ export const RightDrawerEmailThread = () => {
|
|||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<EmailThreadHeader
|
<EmailThreadHeader
|
||||||
subject={viewableEmailThread.subject}
|
subject={viewableEmailThread.subject}
|
||||||
lastMessageSentAt={viewableEmailThread.receivedAt}
|
lastMessageSentAt={viewableEmailThread.lastMessageReceivedAt}
|
||||||
/>
|
/>
|
||||||
{messages.map((message) => (
|
{messages.map((message) => (
|
||||||
<EmailThreadMessage
|
<EmailThreadMessage
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { atom } from 'recoil';
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
import { MockedThread } from '@/activities/emails/mocks/mockedEmailThreads';
|
import { TimelineThread } from '~/generated/graphql';
|
||||||
|
|
||||||
export const viewableEmailThreadState = atom<MockedThread | null>({
|
export const viewableEmailThreadState = atom<TimelineThread | null>({
|
||||||
key: 'viewableEmailThreadState',
|
key: 'viewableEmailThreadState',
|
||||||
default: null,
|
default: null,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { Comment } from '@/activities/types/Comment';
|
|||||||
import { Company } from '@/companies/types/Company';
|
import { Company } from '@/companies/types/Company';
|
||||||
import { Person } from '@/people/types/Person';
|
import { Person } from '@/people/types/Person';
|
||||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||||
import { TimelineThread } from '~/generated/graphql';
|
|
||||||
|
|
||||||
type MockedActivity = Pick<
|
type MockedActivity = Pick<
|
||||||
Activity,
|
Activity,
|
||||||
@ -211,24 +210,3 @@ export const mockedActivities: Array<MockedActivity> = [
|
|||||||
__typename: 'Activity',
|
__typename: 'Activity',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const mockedEmailThreads: TimelineThread[] = [
|
|
||||||
{
|
|
||||||
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dignissim nisi eu tellus dapibus, egestas placerat risus placerat. Praesent eget arcu consectetur, efficitur felis.',
|
|
||||||
numberOfMessagesInThread: 4,
|
|
||||||
read: false,
|
|
||||||
receivedAt: new Date('11/04/2023').toISOString(),
|
|
||||||
senderName: 'Steve Anahi',
|
|
||||||
senderPictureUrl: '',
|
|
||||||
subject: 'Partnerships',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dignissim nisi eu tellus dapibus, egestas placerat risus placerat. Praesent eget arcu consectetur, efficitur felis.',
|
|
||||||
numberOfMessagesInThread: 3,
|
|
||||||
read: true,
|
|
||||||
receivedAt: new Date('11/04/2023').toISOString(),
|
|
||||||
senderName: 'Alexandre Prot',
|
|
||||||
senderPictureUrl: '',
|
|
||||||
subject: 'Next step',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
export const TIMELINE_THREADS_DEFAULT_PAGE_SIZE = 20;
|
||||||
|
export const TIMELINE_THREADS_MAX_PAGE_SIZE = 50;
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { ObjectType, Field, ID } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
@ObjectType('TimelineThreadParticipant')
|
||||||
|
export class TimelineThreadParticipant {
|
||||||
|
@Field(() => ID, { nullable: true })
|
||||||
|
personId: string;
|
||||||
|
|
||||||
|
@Field(() => ID, { nullable: true })
|
||||||
|
workspaceMemberId: string;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
firstName: string;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
lastName: string;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
displayName: string;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
avatarUrl: string;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
handle: string;
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import { ObjectType, Field, ID } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||||
|
|
||||||
|
import { TimelineThreadParticipant } from 'src/core/messaging/dtos/timeline-thread-participant.dto';
|
||||||
|
|
||||||
|
@ObjectType('TimelineThread')
|
||||||
|
export class TimelineThread {
|
||||||
|
@IDField(() => ID)
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
read: boolean;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
firstParticipant: TimelineThreadParticipant;
|
||||||
|
|
||||||
|
@Field(() => [TimelineThreadParticipant])
|
||||||
|
lastTwoParticipants: TimelineThreadParticipant[];
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
lastMessageReceivedAt: Date;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
lastMessageBody: string;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
subject: string;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
numberOfMessagesInThread: number;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
participantCount: number;
|
||||||
|
}
|
||||||
@ -1,43 +1,47 @@
|
|||||||
import { Args, Query, Field, Resolver, ObjectType } from '@nestjs/graphql';
|
import {
|
||||||
|
Args,
|
||||||
|
Query,
|
||||||
|
Resolver,
|
||||||
|
Int,
|
||||||
|
ArgsType,
|
||||||
|
Field,
|
||||||
|
ID,
|
||||||
|
} from '@nestjs/graphql';
|
||||||
import { UseGuards } from '@nestjs/common';
|
import { UseGuards } from '@nestjs/common';
|
||||||
|
|
||||||
import { Column, Entity } from 'typeorm';
|
import { Max } from 'class-validator';
|
||||||
|
|
||||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||||
import { Workspace } from 'src/core/workspace/workspace.entity';
|
import { Workspace } from 'src/core/workspace/workspace.entity';
|
||||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||||
import { TimelineMessagingService } from 'src/core/messaging/timeline-messaging.service';
|
import { TimelineMessagingService } from 'src/core/messaging/timeline-messaging.service';
|
||||||
|
import { TimelineThread } from 'src/core/messaging/dtos/timeline-thread.dto';
|
||||||
|
import { TIMELINE_THREADS_MAX_PAGE_SIZE } from 'src/core/messaging/constants/messaging.constants';
|
||||||
|
|
||||||
@Entity({ name: 'timelineThread', schema: 'core' })
|
@ArgsType()
|
||||||
@ObjectType('TimelineThread')
|
class GetTimelineThreadsFromPersonIdArgs {
|
||||||
export class TimelineThread {
|
@Field(() => ID)
|
||||||
@Field()
|
personId: string;
|
||||||
@Column()
|
|
||||||
read: boolean;
|
|
||||||
|
|
||||||
@Field()
|
@Field(() => Int)
|
||||||
@Column()
|
page: number;
|
||||||
senderName: string;
|
|
||||||
|
|
||||||
@Field()
|
@Field(() => Int)
|
||||||
@Column()
|
@Max(TIMELINE_THREADS_MAX_PAGE_SIZE)
|
||||||
senderPictureUrl: string;
|
pageSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
@Field()
|
@ArgsType()
|
||||||
@Column()
|
class GetTimelineThreadsFromCompanyIdArgs {
|
||||||
numberOfMessagesInThread: number;
|
@Field(() => ID)
|
||||||
|
companyId: string;
|
||||||
|
|
||||||
@Field()
|
@Field(() => Int)
|
||||||
@Column()
|
page: number;
|
||||||
subject: string;
|
|
||||||
|
|
||||||
@Field()
|
@Field(() => Int)
|
||||||
@Column()
|
@Max(TIMELINE_THREADS_MAX_PAGE_SIZE)
|
||||||
body: string;
|
pageSize: number;
|
||||||
|
|
||||||
@Field()
|
|
||||||
@Column()
|
|
||||||
receivedAt: Date;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ -50,12 +54,14 @@ export class TimelineMessagingResolver {
|
|||||||
@Query(() => [TimelineThread])
|
@Query(() => [TimelineThread])
|
||||||
async getTimelineThreadsFromPersonId(
|
async getTimelineThreadsFromPersonId(
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
@Args('personId') personId: string,
|
@Args() { personId, page, pageSize }: GetTimelineThreadsFromPersonIdArgs,
|
||||||
) {
|
) {
|
||||||
const timelineThreads =
|
const timelineThreads =
|
||||||
await this.timelineMessagingService.getMessagesFromPersonIds(
|
await this.timelineMessagingService.getMessagesFromPersonIds(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
[personId],
|
[personId],
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
);
|
);
|
||||||
|
|
||||||
return timelineThreads;
|
return timelineThreads;
|
||||||
@ -64,12 +70,14 @@ export class TimelineMessagingResolver {
|
|||||||
@Query(() => [TimelineThread])
|
@Query(() => [TimelineThread])
|
||||||
async getTimelineThreadsFromCompanyId(
|
async getTimelineThreadsFromCompanyId(
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
@Args('companyId') companyId: string,
|
@Args() { companyId, page, pageSize }: GetTimelineThreadsFromCompanyIdArgs,
|
||||||
) {
|
) {
|
||||||
const timelineThreads =
|
const timelineThreads =
|
||||||
await this.timelineMessagingService.getMessagesFromCompanyId(
|
await this.timelineMessagingService.getMessagesFromCompanyId(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
companyId,
|
companyId,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
);
|
);
|
||||||
|
|
||||||
return timelineThreads;
|
return timelineThreads;
|
||||||
|
|||||||
@ -1,9 +1,20 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { TimelineThread } from 'src/core/messaging/timeline-messaging.resolver';
|
import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from 'src/core/messaging/constants/messaging.constants';
|
||||||
|
import { TimelineThread } from 'src/core/messaging/dtos/timeline-thread.dto';
|
||||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||||
|
|
||||||
|
type TimelineThreadParticipant = {
|
||||||
|
personId: string;
|
||||||
|
workspaceMemberId: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
displayName: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
handle: string;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TimelineMessagingService {
|
export class TimelineMessagingService {
|
||||||
constructor(
|
constructor(
|
||||||
@ -14,7 +25,11 @@ export class TimelineMessagingService {
|
|||||||
async getMessagesFromPersonIds(
|
async getMessagesFromPersonIds(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
personIds: string[],
|
personIds: string[],
|
||||||
|
page: number = 1,
|
||||||
|
pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE,
|
||||||
): Promise<TimelineThread[]> {
|
): Promise<TimelineThread[]> {
|
||||||
|
const offset = (page - 1) * TIMELINE_THREADS_DEFAULT_PAGE_SIZE;
|
||||||
|
|
||||||
const dataSourceMetadata =
|
const dataSourceMetadata =
|
||||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -23,61 +38,327 @@ export class TimelineMessagingService {
|
|||||||
const workspaceDataSource =
|
const workspaceDataSource =
|
||||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
|
|
||||||
// 10 first threads This hard limit is just for the POC, we will implement pagination later
|
const messageThreads:
|
||||||
const messageThreads = await workspaceDataSource?.query(
|
| {
|
||||||
|
id: string;
|
||||||
|
lastMessageReceivedAt: Date;
|
||||||
|
lastMessageId: string;
|
||||||
|
lastMessageBody: string;
|
||||||
|
rowNumber: number;
|
||||||
|
}[]
|
||||||
|
| undefined = await workspaceDataSource?.query(
|
||||||
`
|
`
|
||||||
SELECT
|
SELECT *
|
||||||
subquery.*,
|
FROM
|
||||||
message_count,
|
(SELECT "messageThread".id,
|
||||||
last_message_subject,
|
MAX(message."receivedAt") AS "lastMessageReceivedAt",
|
||||||
last_message_text,
|
message.id AS "lastMessageId",
|
||||||
last_message_received_at,
|
message.text AS "lastMessageBody",
|
||||||
last_message_participant_handle,
|
ROW_NUMBER() OVER (PARTITION BY "messageThread".id ORDER BY MAX(message."receivedAt") DESC) AS "rowNumber"
|
||||||
last_message_participant_displayName
|
FROM
|
||||||
FROM (
|
${dataSourceMetadata.schema}."message" message
|
||||||
SELECT
|
LEFT JOIN
|
||||||
mt.*,
|
${dataSourceMetadata.schema}."messageThread" "messageThread" ON "messageThread".id = message."messageThreadId"
|
||||||
COUNT(m."id") OVER (PARTITION BY mt."id") AS message_count,
|
LEFT JOIN
|
||||||
FIRST_VALUE(m."subject") OVER (PARTITION BY mt."id" ORDER BY m."receivedAt" DESC) AS last_message_subject,
|
${dataSourceMetadata.schema}."messageParticipant" "messageParticipant" ON "messageParticipant"."messageId" = message.id
|
||||||
FIRST_VALUE(m."text") OVER (PARTITION BY mt."id" ORDER BY m."receivedAt" DESC) AS last_message_text,
|
LEFT JOIN
|
||||||
FIRST_VALUE(m."receivedAt") OVER (PARTITION BY mt."id" ORDER BY m."receivedAt" DESC) AS last_message_received_at,
|
${dataSourceMetadata.schema}."person" person ON person.id = "messageParticipant"."personId"
|
||||||
FIRST_VALUE(mr."handle") OVER (PARTITION BY mt."id" ORDER BY m."receivedAt" DESC) AS last_message_participant_handle,
|
LEFT JOIN
|
||||||
FIRST_VALUE(mr."displayName") OVER (PARTITION BY mt."id" ORDER BY m."receivedAt" DESC) AS last_message_participant_displayName,
|
${dataSourceMetadata.schema}."workspaceMember" "workspaceMember" ON "workspaceMember".id = "messageParticipant"."workspaceMemberId"
|
||||||
ROW_NUMBER() OVER (PARTITION BY mt."id" ORDER BY m."receivedAt" DESC) AS rn
|
WHERE
|
||||||
FROM
|
person.id = ANY($1)
|
||||||
${dataSourceMetadata.schema}."messageThread" mt
|
GROUP BY
|
||||||
LEFT JOIN
|
"messageThread".id,
|
||||||
${dataSourceMetadata.schema}."message" m ON mt."id" = m."messageThreadId"
|
message.id
|
||||||
LEFT JOIN
|
ORDER BY
|
||||||
${dataSourceMetadata.schema}."messageParticipant" mr ON m."id" = mr."messageId"
|
message."receivedAt" DESC
|
||||||
WHERE
|
) AS "messageThreads"
|
||||||
mr."personId" IN (SELECT unnest($1::uuid[]))
|
WHERE
|
||||||
) AS subquery
|
"rowNumber" = 1
|
||||||
WHERE
|
LIMIT $2
|
||||||
subquery.rn = 1
|
OFFSET $3
|
||||||
ORDER BY
|
`,
|
||||||
subquery.last_message_received_at DESC
|
[personIds, pageSize, offset],
|
||||||
LIMIT 10;
|
|
||||||
`,
|
|
||||||
[personIds],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const formattedMessageThreads = messageThreads.map((messageThread) => {
|
if (!messageThreads) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageThreadIds = messageThreads.map(
|
||||||
|
(messageThread) => messageThread.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const threadSubjects:
|
||||||
|
| {
|
||||||
|
id: string;
|
||||||
|
subject: string;
|
||||||
|
}[]
|
||||||
|
| undefined = await workspaceDataSource?.query(
|
||||||
|
`
|
||||||
|
SELECT *
|
||||||
|
FROM
|
||||||
|
(SELECT
|
||||||
|
"messageThread".id,
|
||||||
|
message.subject,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY "messageThread".id ORDER BY MAX(message."receivedAt") ASC) AS "rowNumber"
|
||||||
|
FROM
|
||||||
|
${dataSourceMetadata.schema}."message" message
|
||||||
|
LEFT JOIN
|
||||||
|
${dataSourceMetadata.schema}."messageThread" "messageThread" ON "messageThread".id = message."messageThreadId"
|
||||||
|
WHERE
|
||||||
|
"messageThread".id = ANY($1)
|
||||||
|
GROUP BY
|
||||||
|
"messageThread".id,
|
||||||
|
message.id
|
||||||
|
ORDER BY
|
||||||
|
message."receivedAt" DESC
|
||||||
|
) AS "messageThreads"
|
||||||
|
WHERE
|
||||||
|
"rowNumber" = 1
|
||||||
|
`,
|
||||||
|
[messageThreadIds],
|
||||||
|
);
|
||||||
|
|
||||||
|
const numberOfMessagesInThread:
|
||||||
|
| {
|
||||||
|
id: string;
|
||||||
|
numberOfMessagesInThread: number;
|
||||||
|
}[]
|
||||||
|
| undefined = await workspaceDataSource?.query(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
"messageThread".id,
|
||||||
|
COUNT(message.id) AS "numberOfMessagesInThread"
|
||||||
|
FROM
|
||||||
|
${dataSourceMetadata.schema}."message" message
|
||||||
|
LEFT JOIN
|
||||||
|
${dataSourceMetadata.schema}."messageThread" "messageThread" ON "messageThread".id = message."messageThreadId"
|
||||||
|
WHERE
|
||||||
|
"messageThread".id = ANY($1)
|
||||||
|
GROUP BY
|
||||||
|
"messageThread".id
|
||||||
|
`,
|
||||||
|
[messageThreadIds],
|
||||||
|
);
|
||||||
|
|
||||||
|
const messageThreadsByMessageThreadId: {
|
||||||
|
[key: string]: {
|
||||||
|
id: string;
|
||||||
|
lastMessageReceivedAt: Date;
|
||||||
|
lastMessageBody: string;
|
||||||
|
};
|
||||||
|
} = messageThreads.reduce((messageThreadAcc, messageThread) => {
|
||||||
|
messageThreadAcc[messageThread.id] = messageThread;
|
||||||
|
|
||||||
|
return messageThreadAcc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const subjectsByMessageThreadId:
|
||||||
|
| {
|
||||||
|
[key: string]: {
|
||||||
|
id: string;
|
||||||
|
subject: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| undefined = threadSubjects?.reduce(
|
||||||
|
(threadSubjectAcc, threadSubject) => {
|
||||||
|
threadSubjectAcc[threadSubject.id] = threadSubject;
|
||||||
|
|
||||||
|
return threadSubjectAcc;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
const numberOfMessagesByMessageThreadId:
|
||||||
|
| {
|
||||||
|
[key: string]: {
|
||||||
|
id: string;
|
||||||
|
numberOfMessagesInThread: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| undefined = numberOfMessagesInThread?.reduce(
|
||||||
|
(numberOfMessagesAcc, numberOfMessages) => {
|
||||||
|
numberOfMessagesAcc[numberOfMessages.id] = numberOfMessages;
|
||||||
|
|
||||||
|
return numberOfMessagesAcc;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
const threadMessagesFromActiveParticipants:
|
||||||
|
| {
|
||||||
|
id: string;
|
||||||
|
messageId: string;
|
||||||
|
receivedAt: Date;
|
||||||
|
body: string;
|
||||||
|
subject: string;
|
||||||
|
personId: string;
|
||||||
|
workspaceMemberId: string;
|
||||||
|
handle: string;
|
||||||
|
personFirstName: string;
|
||||||
|
personLastName: string;
|
||||||
|
personAvatarUrl: string;
|
||||||
|
workspaceMemberFirstName: string;
|
||||||
|
workspaceMemberLastName: string;
|
||||||
|
workspaceMemberAvatarUrl: string;
|
||||||
|
messageDisplayName: string;
|
||||||
|
}[]
|
||||||
|
| undefined = await workspaceDataSource?.query(
|
||||||
|
`
|
||||||
|
SELECT DISTINCT "messageThread".id,
|
||||||
|
message.id AS "messageId",
|
||||||
|
message."receivedAt",
|
||||||
|
message.text,
|
||||||
|
message."subject",
|
||||||
|
"messageParticipant"."personId",
|
||||||
|
"messageParticipant"."workspaceMemberId",
|
||||||
|
"messageParticipant".handle,
|
||||||
|
"person"."nameFirstName" as "personFirstName",
|
||||||
|
"person"."nameLastName" as "personLastName",
|
||||||
|
"person"."avatarUrl" as "personAvatarUrl",
|
||||||
|
"workspaceMember"."nameFirstName" as "workspaceMemberFirstName",
|
||||||
|
"workspaceMember"."nameLastName" as "workspaceMemberLastName",
|
||||||
|
"workspaceMember"."avatarUrl" as "workspaceMemberAvatarUrl",
|
||||||
|
"messageParticipant"."displayName" as "messageDisplayName"
|
||||||
|
FROM
|
||||||
|
${dataSourceMetadata.schema}."message" message
|
||||||
|
LEFT JOIN
|
||||||
|
${dataSourceMetadata.schema}."messageThread" "messageThread" ON "messageThread".id = message."messageThreadId"
|
||||||
|
LEFT JOIN
|
||||||
|
(SELECT * FROM ${dataSourceMetadata.schema}."messageParticipant" WHERE "messageParticipant".role = 'from') "messageParticipant" ON "messageParticipant"."messageId" = message.id
|
||||||
|
LEFT JOIN
|
||||||
|
${dataSourceMetadata.schema}."person" person ON person."id" = "messageParticipant"."personId"
|
||||||
|
LEFT JOIN
|
||||||
|
${dataSourceMetadata.schema}."workspaceMember" "workspaceMember" ON "workspaceMember".id = "messageParticipant"."workspaceMemberId"
|
||||||
|
WHERE
|
||||||
|
"messageThread".id = ANY($1)
|
||||||
|
ORDER BY
|
||||||
|
message."receivedAt" DESC
|
||||||
|
`,
|
||||||
|
[messageThreadIds],
|
||||||
|
);
|
||||||
|
|
||||||
|
const threadParticipantsByThreadId: {
|
||||||
|
[key: string]: TimelineThreadParticipant[];
|
||||||
|
} = messageThreadIds.reduce((messageThreadIdAcc, messageThreadId) => {
|
||||||
|
const threadMessages = threadMessagesFromActiveParticipants?.filter(
|
||||||
|
(threadMessage) => threadMessage.id === messageThreadId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const threadParticipants = threadMessages?.reduce(
|
||||||
|
(
|
||||||
|
threadMessageAcc,
|
||||||
|
threadMessage,
|
||||||
|
): {
|
||||||
|
[key: string]: TimelineThreadParticipant;
|
||||||
|
} => {
|
||||||
|
const threadParticipant = threadMessageAcc[threadMessage.handle];
|
||||||
|
|
||||||
|
const firstName =
|
||||||
|
threadMessage.personFirstName ||
|
||||||
|
threadMessage.workspaceMemberFirstName ||
|
||||||
|
'';
|
||||||
|
|
||||||
|
const lastName =
|
||||||
|
threadMessage.personLastName ||
|
||||||
|
threadMessage.workspaceMemberLastName ||
|
||||||
|
'';
|
||||||
|
|
||||||
|
const displayName =
|
||||||
|
firstName ||
|
||||||
|
threadMessage.messageDisplayName ||
|
||||||
|
threadMessage.handle;
|
||||||
|
|
||||||
|
if (!threadParticipant) {
|
||||||
|
threadMessageAcc[threadMessage.handle] = {
|
||||||
|
personId: threadMessage.personId,
|
||||||
|
workspaceMemberId: threadMessage.workspaceMemberId,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
displayName,
|
||||||
|
avatarUrl:
|
||||||
|
threadMessage.personAvatarUrl ??
|
||||||
|
threadMessage.workspaceMemberAvatarUrl ??
|
||||||
|
'',
|
||||||
|
handle: threadMessage.handle,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return threadMessageAcc;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
messageThreadIdAcc[messageThreadId] = threadParticipants
|
||||||
|
? Object.values(threadParticipants)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return messageThreadIdAcc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const timelineThreads = messageThreadIds.map((messageThreadId) => {
|
||||||
|
const threadParticipants = threadParticipantsByThreadId[messageThreadId];
|
||||||
|
|
||||||
|
const firstParticipant = threadParticipants[0];
|
||||||
|
|
||||||
|
const threadParticipantsWithoutFirstParticipant =
|
||||||
|
threadParticipants.filter(
|
||||||
|
(threadParticipant) =>
|
||||||
|
threadParticipant.handle !== firstParticipant.handle,
|
||||||
|
);
|
||||||
|
|
||||||
|
const lastTwoParticipants: TimelineThreadParticipant[] = [];
|
||||||
|
|
||||||
|
const lastParticipant =
|
||||||
|
threadParticipantsWithoutFirstParticipant.slice(-1)[0];
|
||||||
|
|
||||||
|
if (lastParticipant) {
|
||||||
|
lastTwoParticipants.push(lastParticipant);
|
||||||
|
|
||||||
|
const threadParticipantsWithoutFirstAndLastParticipants =
|
||||||
|
threadParticipantsWithoutFirstParticipant.filter(
|
||||||
|
(threadParticipant) =>
|
||||||
|
threadParticipant.handle !== lastParticipant.handle,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (threadParticipantsWithoutFirstAndLastParticipants.length > 0)
|
||||||
|
lastTwoParticipants.push(
|
||||||
|
threadParticipantsWithoutFirstAndLastParticipants.slice(-1)[0],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const thread = messageThreadsByMessageThreadId[messageThreadId];
|
||||||
|
|
||||||
|
const threadSubject =
|
||||||
|
subjectsByMessageThreadId?.[messageThreadId].subject ?? '';
|
||||||
|
|
||||||
|
const numberOfMessages =
|
||||||
|
numberOfMessagesByMessageThreadId?.[messageThreadId]
|
||||||
|
.numberOfMessagesInThread ?? 1;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: messageThreadId,
|
||||||
read: true,
|
read: true,
|
||||||
senderName: messageThread.last_message_participant_handle,
|
firstParticipant,
|
||||||
senderPictureUrl: '',
|
lastTwoParticipants,
|
||||||
numberOfMessagesInThread: messageThread.message_count,
|
lastMessageReceivedAt: thread.lastMessageReceivedAt,
|
||||||
subject: messageThread.last_message_subject,
|
lastMessageBody: thread.lastMessageBody,
|
||||||
body: messageThread.last_message_text,
|
subject: threadSubject,
|
||||||
receivedAt: messageThread.last_message_received_at,
|
numberOfMessagesInThread: numberOfMessages,
|
||||||
|
participantCount: threadParticipants.length,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return formattedMessageThreads;
|
return timelineThreads;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMessagesFromCompanyId(workspaceId: string, companyId: string) {
|
async getMessagesFromCompanyId(
|
||||||
|
workspaceId: string,
|
||||||
|
companyId: string,
|
||||||
|
page: number = 1,
|
||||||
|
pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE,
|
||||||
|
) {
|
||||||
const dataSourceMetadata =
|
const dataSourceMetadata =
|
||||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -102,11 +383,15 @@ export class TimelineMessagingService {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedPersonIds = personIds.map((personId) => personId.id);
|
const formattedPersonIds = personIds.map(
|
||||||
|
(personId: { id: string }) => personId.id,
|
||||||
|
);
|
||||||
|
|
||||||
const messageThreads = await this.getMessagesFromPersonIds(
|
const messageThreads = await this.getMessagesFromPersonIds(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
formattedPersonIds,
|
formattedPersonIds,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
);
|
);
|
||||||
|
|
||||||
return messageThreads;
|
return messageThreads;
|
||||||
|
|||||||
Reference in New Issue
Block a user