3675 inbox count is wrong in emailthreads (#3677)

* add type

* query total number of threads

* graphql data generate

* wip

* wip

* Fix fetch more

* fix

---------

Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
bosiraphael
2024-01-29 18:23:09 +01:00
committed by GitHub
parent e46085984c
commit c5ea2dfe1e
9 changed files with 116 additions and 46 deletions

View File

@ -378,8 +378,8 @@ export type Query = {
currentUser: User; currentUser: User;
currentWorkspace: Workspace; currentWorkspace: Workspace;
findWorkspaceFromInviteHash: Workspace; findWorkspaceFromInviteHash: Workspace;
getTimelineThreadsFromCompanyId: Array<TimelineThread>; getTimelineThreadsFromCompanyId: TimelineThreadsWithTotal;
getTimelineThreadsFromPersonId: Array<TimelineThread>; getTimelineThreadsFromPersonId: TimelineThreadsWithTotal;
object: Object; object: Object;
objects: ObjectConnection; objects: ObjectConnection;
validatePasswordResetToken: ValidatePasswordResetToken; validatePasswordResetToken: ValidatePasswordResetToken;
@ -517,6 +517,12 @@ export type TimelineThreadParticipant = {
workspaceMemberId?: Maybe<Scalars['ID']>; workspaceMemberId?: Maybe<Scalars['ID']>;
}; };
export type TimelineThreadsWithTotal = {
__typename?: 'TimelineThreadsWithTotal';
timelineThreads: Array<TimelineThread>;
totalNumberOfThreads: Scalars['Int'];
};
export type TransientToken = { export type TransientToken = {
__typename?: 'TransientToken'; __typename?: 'TransientToken';
transientToken: AuthToken; transientToken: AuthToken;
@ -715,6 +721,8 @@ export type ParticipantFragmentFragment = { __typename?: 'TimelineThreadParticip
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 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 TimelineThreadsWithTotalFragmentFragment = { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: 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 GetTimelineThreadsFromCompanyIdQueryVariables = Exact<{ export type GetTimelineThreadsFromCompanyIdQueryVariables = Exact<{
companyId: Scalars['ID']; companyId: Scalars['ID'];
page: Scalars['Int']; page: Scalars['Int'];
@ -722,7 +730,7 @@ export type GetTimelineThreadsFromCompanyIdQueryVariables = Exact<{
}>; }>;
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 GetTimelineThreadsFromCompanyIdQuery = { __typename?: 'Query', getTimelineThreadsFromCompanyId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: 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['ID']; personId: Scalars['ID'];
@ -731,7 +739,7 @@ export type GetTimelineThreadsFromPersonIdQueryVariables = Exact<{
}>; }>;
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 GetTimelineThreadsFromPersonIdQuery = { __typename?: 'Query', getTimelineThreadsFromPersonId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: 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'];
@ -912,6 +920,14 @@ export const TimelineThreadFragmentFragmentDoc = gql`
participantCount participantCount
} }
${ParticipantFragmentFragmentDoc}`; ${ParticipantFragmentFragmentDoc}`;
export const TimelineThreadsWithTotalFragmentFragmentDoc = gql`
fragment TimelineThreadsWithTotalFragment on TimelineThreadsWithTotal {
totalNumberOfThreads
timelineThreads {
...TimelineThreadFragment
}
}
${TimelineThreadFragmentFragmentDoc}`;
export const AuthTokenFragmentFragmentDoc = gql` export const AuthTokenFragmentFragmentDoc = gql`
fragment AuthTokenFragment on AuthToken { fragment AuthTokenFragment on AuthToken {
token token
@ -970,10 +986,10 @@ export const GetTimelineThreadsFromCompanyIdDocument = gql`
page: $page page: $page
pageSize: $pageSize pageSize: $pageSize
) { ) {
...TimelineThreadFragment ...TimelineThreadsWithTotalFragment
} }
} }
${TimelineThreadFragmentFragmentDoc}`; ${TimelineThreadsWithTotalFragmentFragmentDoc}`;
/** /**
* __useGetTimelineThreadsFromCompanyIdQuery__ * __useGetTimelineThreadsFromCompanyIdQuery__
@ -1011,10 +1027,10 @@ export const GetTimelineThreadsFromPersonIdDocument = gql`
page: $page page: $page
pageSize: $pageSize pageSize: $pageSize
) { ) {
...TimelineThreadFragment ...TimelineThreadsWithTotalFragment
} }
} }
${TimelineThreadFragmentFragmentDoc}`; ${TimelineThreadsWithTotalFragmentFragmentDoc}`;
/** /**
* __useGetTimelineThreadsFromPersonIdQuery__ * __useGetTimelineThreadsFromPersonIdQuery__

View File

@ -25,6 +25,7 @@ import { FetchMoreLoader } from '@/ui/utilities/loading-state/components/FetchMo
import { import {
GetTimelineThreadsFromPersonIdQueryVariables, GetTimelineThreadsFromPersonIdQueryVariables,
TimelineThread, TimelineThread,
TimelineThreadsWithTotal,
} from '~/generated/graphql'; } from '~/generated/graphql';
const StyledContainer = styled.div` const StyledContainer = styled.div`
@ -85,7 +86,7 @@ export const EmailThreads = ({
page: emailThreadsPage.pageNumber + 1, page: emailThreadsPage.pageNumber + 1,
}, },
updateQuery: (prev, { fetchMoreResult }) => { updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult || !fetchMoreResult?.[queryName].length) { if (!fetchMoreResult?.[queryName]?.timelineThreads?.length) {
setEmailThreadsPage((emailThreadsPage) => ({ setEmailThreadsPage((emailThreadsPage) => ({
...emailThreadsPage, ...emailThreadsPage,
hasNextPage: false, hasNextPage: false,
@ -94,10 +95,13 @@ export const EmailThreads = ({
} }
return { return {
[queryName]: [ [queryName]: {
...(prev?.[queryName] ?? []), ...prev?.[queryName],
...(fetchMoreResult?.[queryName] ?? []), timelineThreads: [
], ...(prev?.[queryName]?.timelineThreads ?? []),
...(fetchMoreResult?.[queryName]?.timelineThreads ?? []),
],
},
}; };
}, },
}); });
@ -115,11 +119,8 @@ export const EmailThreads = ({
}); });
} }
if (loading) { const { totalNumberOfThreads, timelineThreads }: TimelineThreadsWithTotal =
return; data?.[queryName] ?? [];
}
const timelineThreads: TimelineThread[] = data?.[queryName] ?? [];
return ( return (
<StyledContainer> <StyledContainer>
@ -128,21 +129,23 @@ export const EmailThreads = ({
title={ title={
<> <>
Inbox{' '} Inbox{' '}
<StyledEmailCount>{timelineThreads?.length}</StyledEmailCount> <StyledEmailCount>{totalNumberOfThreads ?? 0}</StyledEmailCount>
</> </>
} }
fontColor={H1TitleFontColor.Primary} fontColor={H1TitleFontColor.Primary}
/> />
<Card> {!loading && (
{timelineThreads.map((thread: TimelineThread, index: number) => ( <Card>
<EmailThreadPreview {timelineThreads?.map((thread: TimelineThread, index: number) => (
key={index} <EmailThreadPreview
divider={index < timelineThreads.length - 1} key={index}
thread={thread} divider={index < timelineThreads.length - 1}
onClick={() => openEmailThread(thread)} thread={thread}
/> onClick={() => openEmailThread(thread)}
))} />
</Card> ))}
</Card>
)}
<FetchMoreLoader <FetchMoreLoader
loading={isFetchingMoreEmails} loading={isFetchingMoreEmails}
onLastRowVisible={fetchMoreRecords} onLastRowVisible={fetchMoreRecords}

View File

@ -0,0 +1,13 @@
import { gql } from '@apollo/client';
import { timelineThreadFragment } from '@/activities/emails/queries/fragments/timelineThreadFragment';
export const timelineThreadWithTotalFragment = gql`
fragment TimelineThreadsWithTotalFragment on TimelineThreadsWithTotal {
totalNumberOfThreads
timelineThreads {
...TimelineThreadFragment
}
}
${timelineThreadFragment}
`;

View File

@ -1,6 +1,6 @@
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import { timelineThreadFragment } from '@/activities/emails/queries/fragments/timelineThreadFragment'; import { timelineThreadWithTotalFragment } from '@/activities/emails/queries/fragments/timelineThreadWithTotalFragment';
export const getTimelineThreadsFromCompanyId = gql` export const getTimelineThreadsFromCompanyId = gql`
query GetTimelineThreadsFromCompanyId( query GetTimelineThreadsFromCompanyId(
@ -13,8 +13,8 @@ export const getTimelineThreadsFromCompanyId = gql`
page: $page page: $page
pageSize: $pageSize pageSize: $pageSize
) { ) {
...TimelineThreadFragment ...TimelineThreadsWithTotalFragment
} }
} }
${timelineThreadFragment} ${timelineThreadWithTotalFragment}
`; `;

View File

@ -1,6 +1,6 @@
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import { timelineThreadFragment } from '@/activities/emails/queries/fragments/timelineThreadFragment'; import { timelineThreadWithTotalFragment } from '@/activities/emails/queries/fragments/timelineThreadWithTotalFragment';
export const getTimelineThreadsFromPersonId = gql` export const getTimelineThreadsFromPersonId = gql`
query GetTimelineThreadsFromPersonId( query GetTimelineThreadsFromPersonId(
@ -13,8 +13,8 @@ export const getTimelineThreadsFromPersonId = gql`
page: $page page: $page
pageSize: $pageSize pageSize: $pageSize
) { ) {
...TimelineThreadFragment ...TimelineThreadsWithTotalFragment
} }
} }
${timelineThreadFragment} ${timelineThreadWithTotalFragment}
`; `;

View File

@ -0,0 +1,12 @@
import { Field, Int, ObjectType } from '@nestjs/graphql';
import { TimelineThread } from 'src/core/messaging/dtos/timeline-thread.dto';
@ObjectType('TimelineThreadsWithTotal')
export class TimelineThreadsWithTotal {
@Field(() => Int)
totalNumberOfThreads: number;
@Field(() => [TimelineThread])
timelineThreads: TimelineThread[];
}

View File

@ -15,8 +15,8 @@ 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'; import { TIMELINE_THREADS_MAX_PAGE_SIZE } from 'src/core/messaging/constants/messaging.constants';
import { TimelineThreadsWithTotal } from 'src/core/messaging/dtos/timeline-threads-with-total.dto';
@ArgsType() @ArgsType()
class GetTimelineThreadsFromPersonIdArgs { class GetTimelineThreadsFromPersonIdArgs {
@ -45,13 +45,13 @@ class GetTimelineThreadsFromCompanyIdArgs {
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Resolver(() => [TimelineThread]) @Resolver(() => TimelineThreadsWithTotal)
export class TimelineMessagingResolver { export class TimelineMessagingResolver {
constructor( constructor(
private readonly timelineMessagingService: TimelineMessagingService, private readonly timelineMessagingService: TimelineMessagingService,
) {} ) {}
@Query(() => [TimelineThread]) @Query(() => TimelineThreadsWithTotal)
async getTimelineThreadsFromPersonId( async getTimelineThreadsFromPersonId(
@AuthWorkspace() { id: workspaceId }: Workspace, @AuthWorkspace() { id: workspaceId }: Workspace,
@Args() { personId, page, pageSize }: GetTimelineThreadsFromPersonIdArgs, @Args() { personId, page, pageSize }: GetTimelineThreadsFromPersonIdArgs,
@ -67,7 +67,7 @@ export class TimelineMessagingResolver {
return timelineThreads; return timelineThreads;
} }
@Query(() => [TimelineThread]) @Query(() => TimelineThreadsWithTotal)
async getTimelineThreadsFromCompanyId( async getTimelineThreadsFromCompanyId(
@AuthWorkspace() { id: workspaceId }: Workspace, @AuthWorkspace() { id: workspaceId }: Workspace,
@Args() { companyId, page, pageSize }: GetTimelineThreadsFromCompanyIdArgs, @Args() { companyId, page, pageSize }: GetTimelineThreadsFromCompanyIdArgs,

View File

@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from 'src/core/messaging/constants/messaging.constants'; import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from 'src/core/messaging/constants/messaging.constants';
import { TimelineThread } from 'src/core/messaging/dtos/timeline-thread.dto'; import { TimelineThreadsWithTotal } from 'src/core/messaging/dtos/timeline-threads-with-total.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';
@ -27,7 +27,7 @@ export class TimelineMessagingService {
personIds: string[], personIds: string[],
page: number = 1, page: number = 1,
pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE, pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE,
): Promise<TimelineThread[]> { ): Promise<TimelineThreadsWithTotal> {
const offset = (page - 1) * pageSize; const offset = (page - 1) * pageSize;
const dataSourceMetadata = const dataSourceMetadata =
@ -82,7 +82,10 @@ export class TimelineMessagingService {
); );
if (!messageThreads) { if (!messageThreads) {
return []; return {
totalNumberOfThreads: 0,
timelineThreads: [],
};
} }
const messageThreadIds = messageThreads.map( const messageThreadIds = messageThreads.map(
@ -239,6 +242,23 @@ export class TimelineMessagingService {
[messageThreadIds], [messageThreadIds],
); );
const totalNumberOfThreads = await workspaceDataSource?.query(
`
SELECT COUNT(DISTINCT "messageThread".id)
FROM
${dataSourceMetadata.schema}."message" message
LEFT JOIN
${dataSourceMetadata.schema}."messageThread" "messageThread" ON "messageThread".id = message."messageThreadId"
LEFT JOIN
${dataSourceMetadata.schema}."messageParticipant" "messageParticipant" ON "messageParticipant"."messageId" = message.id
LEFT JOIN
${dataSourceMetadata.schema}."person" person ON person.id = "messageParticipant"."personId"
WHERE
person.id = ANY($1)
`,
[personIds],
);
const threadParticipantsByThreadId: { const threadParticipantsByThreadId: {
[key: string]: TimelineThreadParticipant[]; [key: string]: TimelineThreadParticipant[];
} = messageThreadIds.reduce((messageThreadIdAcc, messageThreadId) => { } = messageThreadIds.reduce((messageThreadIdAcc, messageThreadId) => {
@ -350,7 +370,10 @@ export class TimelineMessagingService {
}; };
}); });
return timelineThreads; return {
totalNumberOfThreads: totalNumberOfThreads[0]?.count ?? 0,
timelineThreads,
};
} }
async getMessagesFromCompanyId( async getMessagesFromCompanyId(
@ -358,7 +381,7 @@ export class TimelineMessagingService {
companyId: string, companyId: string,
page: number = 1, page: number = 1,
pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE, pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE,
) { ): Promise<TimelineThreadsWithTotal> {
const dataSourceMetadata = const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId, workspaceId,
@ -380,7 +403,10 @@ export class TimelineMessagingService {
); );
if (!personIds) { if (!personIds) {
return []; return {
totalNumberOfThreads: 0,
timelineThreads: [],
};
} }
const formattedPersonIds = personIds.map( const formattedPersonIds = personIds.map(