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:
@ -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 { Column, Entity } from 'typeorm';
|
||||
import { Max } from 'class-validator';
|
||||
|
||||
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';
|
||||
|
||||
@Entity({ name: 'timelineThread', schema: 'core' })
|
||||
@ObjectType('TimelineThread')
|
||||
export class TimelineThread {
|
||||
@Field()
|
||||
@Column()
|
||||
read: boolean;
|
||||
@ArgsType()
|
||||
class GetTimelineThreadsFromPersonIdArgs {
|
||||
@Field(() => ID)
|
||||
personId: string;
|
||||
|
||||
@Field()
|
||||
@Column()
|
||||
senderName: string;
|
||||
@Field(() => Int)
|
||||
page: number;
|
||||
|
||||
@Field()
|
||||
@Column()
|
||||
senderPictureUrl: string;
|
||||
@Field(() => Int)
|
||||
@Max(TIMELINE_THREADS_MAX_PAGE_SIZE)
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
@Field()
|
||||
@Column()
|
||||
numberOfMessagesInThread: number;
|
||||
@ArgsType()
|
||||
class GetTimelineThreadsFromCompanyIdArgs {
|
||||
@Field(() => ID)
|
||||
companyId: string;
|
||||
|
||||
@Field()
|
||||
@Column()
|
||||
subject: string;
|
||||
@Field(() => Int)
|
||||
page: number;
|
||||
|
||||
@Field()
|
||||
@Column()
|
||||
body: string;
|
||||
|
||||
@Field()
|
||||
@Column()
|
||||
receivedAt: Date;
|
||||
@Field(() => Int)
|
||||
@Max(TIMELINE_THREADS_MAX_PAGE_SIZE)
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ -50,12 +54,14 @@ export class TimelineMessagingResolver {
|
||||
@Query(() => [TimelineThread])
|
||||
async getTimelineThreadsFromPersonId(
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
@Args('personId') personId: string,
|
||||
@Args() { personId, page, pageSize }: GetTimelineThreadsFromPersonIdArgs,
|
||||
) {
|
||||
const timelineThreads =
|
||||
await this.timelineMessagingService.getMessagesFromPersonIds(
|
||||
workspaceId,
|
||||
[personId],
|
||||
page,
|
||||
pageSize,
|
||||
);
|
||||
|
||||
return timelineThreads;
|
||||
@ -64,12 +70,14 @@ export class TimelineMessagingResolver {
|
||||
@Query(() => [TimelineThread])
|
||||
async getTimelineThreadsFromCompanyId(
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
@Args('companyId') companyId: string,
|
||||
@Args() { companyId, page, pageSize }: GetTimelineThreadsFromCompanyIdArgs,
|
||||
) {
|
||||
const timelineThreads =
|
||||
await this.timelineMessagingService.getMessagesFromCompanyId(
|
||||
workspaceId,
|
||||
companyId,
|
||||
page,
|
||||
pageSize,
|
||||
);
|
||||
|
||||
return timelineThreads;
|
||||
|
||||
@ -1,9 +1,20 @@
|
||||
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 { 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()
|
||||
export class TimelineMessagingService {
|
||||
constructor(
|
||||
@ -14,7 +25,11 @@ export class TimelineMessagingService {
|
||||
async getMessagesFromPersonIds(
|
||||
workspaceId: string,
|
||||
personIds: string[],
|
||||
page: number = 1,
|
||||
pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE,
|
||||
): Promise<TimelineThread[]> {
|
||||
const offset = (page - 1) * TIMELINE_THREADS_DEFAULT_PAGE_SIZE;
|
||||
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
workspaceId,
|
||||
@ -23,61 +38,327 @@ export class TimelineMessagingService {
|
||||
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(
|
||||
const messageThreads:
|
||||
| {
|
||||
id: string;
|
||||
lastMessageReceivedAt: Date;
|
||||
lastMessageId: string;
|
||||
lastMessageBody: string;
|
||||
rowNumber: number;
|
||||
}[]
|
||||
| undefined = await workspaceDataSource?.query(
|
||||
`
|
||||
SELECT
|
||||
subquery.*,
|
||||
message_count,
|
||||
last_message_subject,
|
||||
last_message_text,
|
||||
last_message_received_at,
|
||||
last_message_participant_handle,
|
||||
last_message_participant_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."receivedAt" DESC) AS last_message_subject,
|
||||
FIRST_VALUE(m."text") OVER (PARTITION BY mt."id" ORDER BY m."receivedAt" DESC) AS last_message_text,
|
||||
FIRST_VALUE(m."receivedAt") OVER (PARTITION BY mt."id" ORDER BY m."receivedAt" DESC) AS last_message_received_at,
|
||||
FIRST_VALUE(mr."handle") OVER (PARTITION BY mt."id" ORDER BY m."receivedAt" DESC) AS last_message_participant_handle,
|
||||
FIRST_VALUE(mr."displayName") OVER (PARTITION BY mt."id" ORDER BY m."receivedAt" DESC) AS last_message_participant_displayName,
|
||||
ROW_NUMBER() OVER (PARTITION BY mt."id" ORDER BY m."receivedAt" DESC) AS rn
|
||||
FROM
|
||||
${dataSourceMetadata.schema}."messageThread" mt
|
||||
LEFT JOIN
|
||||
${dataSourceMetadata.schema}."message" m ON mt."id" = m."messageThreadId"
|
||||
LEFT JOIN
|
||||
${dataSourceMetadata.schema}."messageParticipant" 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_received_at DESC
|
||||
LIMIT 10;
|
||||
`,
|
||||
[personIds],
|
||||
SELECT *
|
||||
FROM
|
||||
(SELECT "messageThread".id,
|
||||
MAX(message."receivedAt") AS "lastMessageReceivedAt",
|
||||
message.id AS "lastMessageId",
|
||||
message.text AS "lastMessageBody",
|
||||
ROW_NUMBER() OVER (PARTITION BY "messageThread".id ORDER BY MAX(message."receivedAt") DESC) AS "rowNumber"
|
||||
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"
|
||||
LEFT JOIN
|
||||
${dataSourceMetadata.schema}."workspaceMember" "workspaceMember" ON "workspaceMember".id = "messageParticipant"."workspaceMemberId"
|
||||
WHERE
|
||||
person.id = ANY($1)
|
||||
GROUP BY
|
||||
"messageThread".id,
|
||||
message.id
|
||||
ORDER BY
|
||||
message."receivedAt" DESC
|
||||
) AS "messageThreads"
|
||||
WHERE
|
||||
"rowNumber" = 1
|
||||
LIMIT $2
|
||||
OFFSET $3
|
||||
`,
|
||||
[personIds, pageSize, offset],
|
||||
);
|
||||
|
||||
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 {
|
||||
id: messageThreadId,
|
||||
read: true,
|
||||
senderName: messageThread.last_message_participant_handle,
|
||||
senderPictureUrl: '',
|
||||
numberOfMessagesInThread: messageThread.message_count,
|
||||
subject: messageThread.last_message_subject,
|
||||
body: messageThread.last_message_text,
|
||||
receivedAt: messageThread.last_message_received_at,
|
||||
firstParticipant,
|
||||
lastTwoParticipants,
|
||||
lastMessageReceivedAt: thread.lastMessageReceivedAt,
|
||||
lastMessageBody: thread.lastMessageBody,
|
||||
subject: threadSubject,
|
||||
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 =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
workspaceId,
|
||||
@ -102,11 +383,15 @@ export class TimelineMessagingService {
|
||||
return [];
|
||||
}
|
||||
|
||||
const formattedPersonIds = personIds.map((personId) => personId.id);
|
||||
const formattedPersonIds = personIds.map(
|
||||
(personId: { id: string }) => personId.id,
|
||||
);
|
||||
|
||||
const messageThreads = await this.getMessagesFromPersonIds(
|
||||
workspaceId,
|
||||
formattedPersonIds,
|
||||
page,
|
||||
pageSize,
|
||||
);
|
||||
|
||||
return messageThreads;
|
||||
|
||||
Reference in New Issue
Block a user