From c5ea2dfe1e87fb1f24d0d06b60caeef2d9a935e2 Mon Sep 17 00:00:00 2001 From: bosiraphael <71827178+bosiraphael@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:23:09 +0100 Subject: [PATCH] 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 --- packages/twenty-front/package.json | 2 +- .../twenty-front/src/generated/graphql.tsx | 32 +++++++++---- .../emails/components/EmailThreads.tsx | 45 ++++++++++--------- .../timelineThreadWithTotalFragment.ts | 13 ++++++ .../getTimelineThreadsFromCompanyId.ts | 6 +-- .../queries/getTimelineThreadsFromPersonId.ts | 6 +-- .../dtos/timeline-threads-with-total.dto.ts | 12 +++++ .../messaging/timeline-messaging.resolver.ts | 8 ++-- .../messaging/timeline-messaging.service.ts | 38 +++++++++++++--- 9 files changed, 116 insertions(+), 46 deletions(-) create mode 100644 packages/twenty-front/src/modules/activities/emails/queries/fragments/timelineThreadWithTotalFragment.ts create mode 100644 packages/twenty-server/src/core/messaging/dtos/timeline-threads-with-total.dto.ts diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json index 705592ca8..8414b7817 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -60,4 +60,4 @@ "msw": { "workerDirectory": "public" } -} \ No newline at end of file +} diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index db114055c..de5efda19 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -378,8 +378,8 @@ export type Query = { currentUser: User; currentWorkspace: Workspace; findWorkspaceFromInviteHash: Workspace; - getTimelineThreadsFromCompanyId: Array; - getTimelineThreadsFromPersonId: Array; + getTimelineThreadsFromCompanyId: TimelineThreadsWithTotal; + getTimelineThreadsFromPersonId: TimelineThreadsWithTotal; object: Object; objects: ObjectConnection; validatePasswordResetToken: ValidatePasswordResetToken; @@ -517,6 +517,12 @@ export type TimelineThreadParticipant = { workspaceMemberId?: Maybe; }; +export type TimelineThreadsWithTotal = { + __typename?: 'TimelineThreadsWithTotal'; + timelineThreads: Array; + totalNumberOfThreads: Scalars['Int']; +}; + export type TransientToken = { __typename?: 'TransientToken'; 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 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<{ companyId: Scalars['ID']; 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<{ 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<{ type: Scalars['String']; @@ -912,6 +920,14 @@ export const TimelineThreadFragmentFragmentDoc = gql` participantCount } ${ParticipantFragmentFragmentDoc}`; +export const TimelineThreadsWithTotalFragmentFragmentDoc = gql` + fragment TimelineThreadsWithTotalFragment on TimelineThreadsWithTotal { + totalNumberOfThreads + timelineThreads { + ...TimelineThreadFragment + } +} + ${TimelineThreadFragmentFragmentDoc}`; export const AuthTokenFragmentFragmentDoc = gql` fragment AuthTokenFragment on AuthToken { token @@ -970,10 +986,10 @@ export const GetTimelineThreadsFromCompanyIdDocument = gql` page: $page pageSize: $pageSize ) { - ...TimelineThreadFragment + ...TimelineThreadsWithTotalFragment } } - ${TimelineThreadFragmentFragmentDoc}`; + ${TimelineThreadsWithTotalFragmentFragmentDoc}`; /** * __useGetTimelineThreadsFromCompanyIdQuery__ @@ -1011,10 +1027,10 @@ export const GetTimelineThreadsFromPersonIdDocument = gql` page: $page pageSize: $pageSize ) { - ...TimelineThreadFragment + ...TimelineThreadsWithTotalFragment } } - ${TimelineThreadFragmentFragmentDoc}`; + ${TimelineThreadsWithTotalFragmentFragmentDoc}`; /** * __useGetTimelineThreadsFromPersonIdQuery__ diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx index da03d3439..e17909f75 100644 --- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx +++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreads.tsx @@ -25,6 +25,7 @@ import { FetchMoreLoader } from '@/ui/utilities/loading-state/components/FetchMo import { GetTimelineThreadsFromPersonIdQueryVariables, TimelineThread, + TimelineThreadsWithTotal, } from '~/generated/graphql'; const StyledContainer = styled.div` @@ -85,7 +86,7 @@ export const EmailThreads = ({ page: emailThreadsPage.pageNumber + 1, }, updateQuery: (prev, { fetchMoreResult }) => { - if (!fetchMoreResult || !fetchMoreResult?.[queryName].length) { + if (!fetchMoreResult?.[queryName]?.timelineThreads?.length) { setEmailThreadsPage((emailThreadsPage) => ({ ...emailThreadsPage, hasNextPage: false, @@ -94,10 +95,13 @@ export const EmailThreads = ({ } return { - [queryName]: [ - ...(prev?.[queryName] ?? []), - ...(fetchMoreResult?.[queryName] ?? []), - ], + [queryName]: { + ...prev?.[queryName], + timelineThreads: [ + ...(prev?.[queryName]?.timelineThreads ?? []), + ...(fetchMoreResult?.[queryName]?.timelineThreads ?? []), + ], + }, }; }, }); @@ -115,11 +119,8 @@ export const EmailThreads = ({ }); } - if (loading) { - return; - } - - const timelineThreads: TimelineThread[] = data?.[queryName] ?? []; + const { totalNumberOfThreads, timelineThreads }: TimelineThreadsWithTotal = + data?.[queryName] ?? []; return ( @@ -128,21 +129,23 @@ export const EmailThreads = ({ title={ <> Inbox{' '} - {timelineThreads?.length} + {totalNumberOfThreads ?? 0} } fontColor={H1TitleFontColor.Primary} /> - - {timelineThreads.map((thread: TimelineThread, index: number) => ( - openEmailThread(thread)} - /> - ))} - + {!loading && ( + + {timelineThreads?.map((thread: TimelineThread, index: number) => ( + openEmailThread(thread)} + /> + ))} + + )} Int) + totalNumberOfThreads: number; + + @Field(() => [TimelineThread]) + timelineThreads: TimelineThread[]; +} diff --git a/packages/twenty-server/src/core/messaging/timeline-messaging.resolver.ts b/packages/twenty-server/src/core/messaging/timeline-messaging.resolver.ts index d77b043d6..ef0199cb9 100644 --- a/packages/twenty-server/src/core/messaging/timeline-messaging.resolver.ts +++ b/packages/twenty-server/src/core/messaging/timeline-messaging.resolver.ts @@ -15,8 +15,8 @@ import { JwtAuthGuard } from 'src/guards/jwt.auth.guard'; import { Workspace } from 'src/core/workspace/workspace.entity'; import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator'; 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 { TimelineThreadsWithTotal } from 'src/core/messaging/dtos/timeline-threads-with-total.dto'; @ArgsType() class GetTimelineThreadsFromPersonIdArgs { @@ -45,13 +45,13 @@ class GetTimelineThreadsFromCompanyIdArgs { } @UseGuards(JwtAuthGuard) -@Resolver(() => [TimelineThread]) +@Resolver(() => TimelineThreadsWithTotal) export class TimelineMessagingResolver { constructor( private readonly timelineMessagingService: TimelineMessagingService, ) {} - @Query(() => [TimelineThread]) + @Query(() => TimelineThreadsWithTotal) async getTimelineThreadsFromPersonId( @AuthWorkspace() { id: workspaceId }: Workspace, @Args() { personId, page, pageSize }: GetTimelineThreadsFromPersonIdArgs, @@ -67,7 +67,7 @@ export class TimelineMessagingResolver { return timelineThreads; } - @Query(() => [TimelineThread]) + @Query(() => TimelineThreadsWithTotal) async getTimelineThreadsFromCompanyId( @AuthWorkspace() { id: workspaceId }: Workspace, @Args() { companyId, page, pageSize }: GetTimelineThreadsFromCompanyIdArgs, diff --git a/packages/twenty-server/src/core/messaging/timeline-messaging.service.ts b/packages/twenty-server/src/core/messaging/timeline-messaging.service.ts index 7a9c48759..2ea042be9 100644 --- a/packages/twenty-server/src/core/messaging/timeline-messaging.service.ts +++ b/packages/twenty-server/src/core/messaging/timeline-messaging.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; 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 { DataSourceService } from 'src/metadata/data-source/data-source.service'; @@ -27,7 +27,7 @@ export class TimelineMessagingService { personIds: string[], page: number = 1, pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE, - ): Promise { + ): Promise { const offset = (page - 1) * pageSize; const dataSourceMetadata = @@ -82,7 +82,10 @@ export class TimelineMessagingService { ); if (!messageThreads) { - return []; + return { + totalNumberOfThreads: 0, + timelineThreads: [], + }; } const messageThreadIds = messageThreads.map( @@ -239,6 +242,23 @@ export class TimelineMessagingService { [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: { [key: string]: TimelineThreadParticipant[]; } = messageThreadIds.reduce((messageThreadIdAcc, messageThreadId) => { @@ -350,7 +370,10 @@ export class TimelineMessagingService { }; }); - return timelineThreads; + return { + totalNumberOfThreads: totalNumberOfThreads[0]?.count ?? 0, + timelineThreads, + }; } async getMessagesFromCompanyId( @@ -358,7 +381,7 @@ export class TimelineMessagingService { companyId: string, page: number = 1, pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE, - ) { + ): Promise { const dataSourceMetadata = await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( workspaceId, @@ -380,7 +403,10 @@ export class TimelineMessagingService { ); if (!personIds) { - return []; + return { + totalNumberOfThreads: 0, + timelineThreads: [], + }; } const formattedPersonIds = personIds.map(