2929 fetch emails from backend and display them in the UI (#3092)

* sending mock data from the resolver

* add sql raw query to the resolver

* improve query

* fix email component css

* fix query

* css adjustments

* create hard limit for mail display

* fix display name ellipsis

* add service

* fetching email on company page is working

* graphql generate

* move queries into separate files

* add types

* renaming

* add early return

* modified according to comments

* graphql data generate

* fix bug after renaming

* fix issue with mock data
This commit is contained in:
bosiraphael
2023-12-21 18:21:07 +01:00
committed by GitHub
parent 7f66eb9459
commit 1b7580476d
15 changed files with 489 additions and 93 deletions

View File

@ -7,6 +7,7 @@ import { AuthModule } from 'src/core/auth/auth.module';
import { ApiRestModule } from 'src/core/api-rest/api-rest.module';
import { FeatureFlagModule } from 'src/core/feature-flag/feature-flag.module';
import { OpenApiModule } from 'src/core/open-api/open-api.module';
import { TimelineMessagingModule } from 'src/core/messaging/timeline-messaging.module';
import { AnalyticsModule } from './analytics/analytics.module';
import { FileModule } from './file/file.module';
@ -24,6 +25,7 @@ import { ClientConfigModule } from './client-config/client-config.module';
ApiRestModule,
OpenApiModule,
FeatureFlagModule,
TimelineMessagingModule,
],
exports: [
AuthModule,
@ -31,6 +33,7 @@ import { ClientConfigModule } from './client-config/client-config.module';
UserModule,
AnalyticsModule,
FeatureFlagModule,
TimelineMessagingModule,
],
})
export class CoreModule {}

View File

@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TimelineMessagingResolver } from 'src/core/messaging/timeline-messaging.resolver';
import { TimelineMessagingService } from 'src/core/messaging/timeline-messaging.service';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
@Module({
imports: [DataSourceModule, TypeORMModule],
exports: [],
providers: [TimelineMessagingResolver, TimelineMessagingService],
})
export class TimelineMessagingModule {}

View File

@ -0,0 +1,77 @@
import { Args, Query, Field, Resolver, ObjectType } from '@nestjs/graphql';
import { UseGuards } from '@nestjs/common';
import { Column, Entity } from 'typeorm';
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';
@Entity({ name: 'timelineThread', schema: 'core' })
@ObjectType('TimelineThread')
class TimelineThread {
@Field()
@Column()
read: boolean;
@Field()
@Column()
senderName: string;
@Field()
@Column()
senderPictureUrl: string;
@Field()
@Column()
numberOfMessagesInThread: number;
@Field()
@Column()
subject: string;
@Field()
@Column()
body: string;
@Field()
@Column()
receivedAt: Date;
}
@UseGuards(JwtAuthGuard)
@Resolver(() => [TimelineThread])
export class TimelineMessagingResolver {
constructor(
private readonly timelineMessagingService: TimelineMessagingService,
) {}
@Query(() => [TimelineThread])
async getTimelineThreadsFromPersonId(
@AuthWorkspace() { id: workspaceId }: Workspace,
@Args('personId') personId: string,
) {
const timelineThreads =
await this.timelineMessagingService.getMessagesFromPersonIds(
workspaceId,
[personId],
);
return timelineThreads;
}
@Query(() => [TimelineThread])
async getTimelineThreadsFromCompanyId(
@AuthWorkspace() { id: workspaceId }: Workspace,
@Args('companyId') companyId: string,
) {
const timelineThreads =
await this.timelineMessagingService.getMessagesFromCompanyId(
workspaceId,
companyId,
);
return timelineThreads;
}
}

View File

@ -0,0 +1,112 @@
import { Injectable } from '@nestjs/common';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
@Injectable()
export class TimelineMessagingService {
constructor(
private readonly dataSourceService: DataSourceService,
private readonly typeORMService: TypeORMService,
) {}
async getMessagesFromPersonIds(workspaceId: string, personIds: string[]) {
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId,
);
const workspaceDataSource = await this.typeORMService.connectToDataSource(
dataSourceMetadata,
);
// 10 first threads This hard limit is just for the POC, we will implement pagination later
const messageThreads = await workspaceDataSource?.query(
`
SELECT
subquery.*,
message_count,
last_message_subject,
last_message_body,
last_message_date,
last_message_recipient_handle,
last_message_recipient_displayName
FROM (
SELECT
mt.*,
COUNT(m."id") OVER (PARTITION BY mt."id") AS message_count,
FIRST_VALUE(m."subject") OVER (PARTITION BY mt."id" ORDER BY m."date" DESC) AS last_message_subject,
FIRST_VALUE(m."body") OVER (PARTITION BY mt."id" ORDER BY m."date" DESC) AS last_message_body,
FIRST_VALUE(m."date") OVER (PARTITION BY mt."id" ORDER BY m."date" DESC) AS last_message_date,
FIRST_VALUE(mr."handle") OVER (PARTITION BY mt."id" ORDER BY m."date" DESC) AS last_message_recipient_handle,
FIRST_VALUE(mr."displayName") OVER (PARTITION BY mt."id" ORDER BY m."date" DESC) AS last_message_recipient_displayName,
ROW_NUMBER() OVER (PARTITION BY mt."id" ORDER BY m."date" DESC) AS rn
FROM
${dataSourceMetadata.schema}."messageThread" mt
LEFT JOIN
${dataSourceMetadata.schema}."message" m ON mt."id" = m."messageThreadId"
LEFT JOIN
${dataSourceMetadata.schema}."messageRecipient" mr ON m."id" = mr."messageId"
WHERE
mr."personId" IN (SELECT unnest($1::uuid[]))
) AS subquery
WHERE
subquery.rn = 1
ORDER BY
subquery.last_message_date DESC
LIMIT 10;
`,
[personIds],
);
const formattedMessageThreads = messageThreads.map((messageThread) => {
return {
read: true,
senderName: messageThread.last_message_recipient_handle,
senderPictureUrl: '',
numberOfMessagesInThread: messageThread.message_count,
subject: messageThread.last_message_subject,
body: messageThread.last_message_body,
receivedAt: messageThread.last_message_date,
};
});
return formattedMessageThreads;
}
async getMessagesFromCompanyId(workspaceId: string, companyId: string) {
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId,
);
const workspaceDataSource = await this.typeORMService.connectToDataSource(
dataSourceMetadata,
);
const personIds = await workspaceDataSource?.query(
`
SELECT
p."id"
FROM
${dataSourceMetadata.schema}."person" p
WHERE
p."companyId" = $1
`,
[companyId],
);
if (!personIds) {
return [];
}
const formattedPersonIds = personIds.map((personId) => personId.id);
const messageThreads = await this.getMessagesFromPersonIds(
workspaceId,
formattedPersonIds,
);
return messageThreads;
}
}