[messaging] Fix thread cleaner service subqueries (#4416)
* [messaging] Fix thread cleaner service subqueries * add pagination * various fixes * Fix thread merging * fix * fix
This commit is contained in:
@ -46,11 +46,19 @@ export class GmailFullSyncService {
|
||||
connectedAccountId: string,
|
||||
nextPageToken?: string,
|
||||
): Promise<void> {
|
||||
const connectedAccount = await this.connectedAccountService.getByIdOrFail(
|
||||
const connectedAccount = await this.connectedAccountService.getById(
|
||||
connectedAccountId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!connectedAccount) {
|
||||
this.logger.error(
|
||||
`Connected account ${connectedAccountId} not found in workspace ${workspaceId} during full-sync`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const accessToken = connectedAccount.accessToken;
|
||||
const refreshToken = connectedAccount.refreshToken;
|
||||
const workspaceMemberId = connectedAccount.accountOwnerId;
|
||||
@ -62,11 +70,19 @@ export class GmailFullSyncService {
|
||||
}
|
||||
|
||||
const gmailMessageChannel =
|
||||
await this.messageChannelService.getFirstByConnectedAccountIdOrFail(
|
||||
await this.messageChannelService.getFirstByConnectedAccountId(
|
||||
connectedAccountId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!gmailMessageChannel) {
|
||||
this.logger.error(
|
||||
`No message channel found for connected account ${connectedAccountId} in workspace ${workspaceId} during full-syn`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const gmailMessageChannelId = gmailMessageChannel.id;
|
||||
|
||||
const gmailClient =
|
||||
@ -173,7 +189,7 @@ export class GmailFullSyncService {
|
||||
);
|
||||
|
||||
if (messagesToSave.length > 0) {
|
||||
this.saveMessagesAndCreateContactsService.saveMessagesAndCreateContacts(
|
||||
await this.saveMessagesAndCreateContactsService.saveMessagesAndCreateContacts(
|
||||
messagesToSave,
|
||||
connectedAccount,
|
||||
workspaceId,
|
||||
|
||||
@ -48,11 +48,19 @@ export class GmailPartialSyncService {
|
||||
connectedAccountId: string,
|
||||
maxResults = 500,
|
||||
): Promise<void> {
|
||||
const connectedAccount = await this.connectedAccountService.getByIdOrFail(
|
||||
const connectedAccount = await this.connectedAccountService.getById(
|
||||
connectedAccountId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!connectedAccount) {
|
||||
this.logger.error(
|
||||
`Connected account ${connectedAccountId} not found in workspace ${workspaceId} during partial-sync`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const lastSyncHistoryId = connectedAccount.lastSyncHistoryId;
|
||||
|
||||
if (!lastSyncHistoryId) {
|
||||
@ -135,11 +143,19 @@ export class GmailPartialSyncService {
|
||||
}
|
||||
|
||||
const gmailMessageChannel =
|
||||
await this.messageChannelService.getFirstByConnectedAccountIdOrFail(
|
||||
await this.messageChannelService.getFirstByConnectedAccountId(
|
||||
connectedAccountId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!gmailMessageChannel) {
|
||||
this.logger.error(
|
||||
`No message channel found for connected account ${connectedAccountId} in workspace ${workspaceId} during partial-sync`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const gmailMessageChannelId = gmailMessageChannel.id;
|
||||
|
||||
const { messagesAdded, messagesDeleted } =
|
||||
|
||||
@ -16,11 +16,17 @@ export class GmailRefreshAccessTokenService {
|
||||
workspaceId: string,
|
||||
connectedAccountId: string,
|
||||
): Promise<void> {
|
||||
const connectedAccount = await this.connectedAccountService.getByIdOrFail(
|
||||
const connectedAccount = await this.connectedAccountService.getById(
|
||||
connectedAccountId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!connectedAccount) {
|
||||
throw new Error(
|
||||
`No connected account found for ${connectedAccountId} in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const refreshToken = connectedAccount.refreshToken;
|
||||
|
||||
if (!refreshToken) {
|
||||
|
||||
@ -59,12 +59,23 @@ export class SaveMessagesAndCreateContactsService {
|
||||
} in ${endTime - startTime}ms`,
|
||||
);
|
||||
|
||||
const isContactAutoCreationEnabled =
|
||||
await this.messageChannelService.getIsContactAutoCreationEnabledByConnectedAccountIdOrFail(
|
||||
const gmailMessageChannel =
|
||||
await this.messageChannelService.getFirstByConnectedAccountId(
|
||||
connectedAccount.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!gmailMessageChannel) {
|
||||
this.logger.error(
|
||||
`No message channel found for connected account ${connectedAccount.id} in workspace ${workspaceId} in saveMessagesAndCreateContacts`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const isContactAutoCreationEnabled =
|
||||
gmailMessageChannel.isContactAutoCreationEnabled;
|
||||
|
||||
const participantsWithMessageId: ParticipantWithMessageId[] =
|
||||
messagesToSave.flatMap((message) => {
|
||||
const messageId = messageExternalIdsAndIdsMap.get(message.externalId);
|
||||
|
||||
@ -4,6 +4,7 @@ import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||
import { MessageThreadService } from 'src/workspace/messaging/repositories/message-thread/message-thread.service';
|
||||
import { MessageService } from 'src/workspace/messaging/repositories/message/message.service';
|
||||
import { deleteUsingPagination } from 'src/workspace/messaging/services/thread-cleaner/utils/delete-using-pagination.util';
|
||||
|
||||
@Injectable()
|
||||
export class ThreadCleanerService {
|
||||
@ -15,48 +16,22 @@ export class ThreadCleanerService {
|
||||
) {}
|
||||
|
||||
public async cleanWorkspaceThreads(workspaceId: string) {
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
workspaceId,
|
||||
);
|
||||
await deleteUsingPagination(
|
||||
workspaceId,
|
||||
500,
|
||||
this.messageService.getNonAssociatedMessageIdsPaginated.bind(
|
||||
this.messageService,
|
||||
),
|
||||
this.messageService.deleteByIds.bind(this.messageService),
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
|
||||
await workspaceDataSource?.transaction(async (transactionManager) => {
|
||||
const messagesToDelete =
|
||||
await this.messageService.getNonAssociatedMessages(
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
const messageIdsToDelete = messagesToDelete.map(({ id }) => id);
|
||||
|
||||
if (messageIdsToDelete.length > 0) {
|
||||
await this.messageService.deleteByIds(
|
||||
messageIdsToDelete,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
|
||||
const messageThreadsToDelete =
|
||||
await this.messageThreadService.getOrphanThreads(
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
const messageThreadToDeleteIds = messageThreadsToDelete.map(
|
||||
({ id }) => id,
|
||||
);
|
||||
|
||||
if (messageThreadToDeleteIds.length > 0) {
|
||||
await this.messageThreadService.deleteByIds(
|
||||
messageThreadToDeleteIds,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
});
|
||||
await deleteUsingPagination(
|
||||
workspaceId,
|
||||
500,
|
||||
this.messageThreadService.getOrphanThreadIdsPaginated.bind(
|
||||
this.messageThreadService,
|
||||
),
|
||||
this.messageThreadService.deleteByIds.bind(this.messageThreadService),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
import { deleteUsingPagination } from './delete-using-pagination.util';
|
||||
|
||||
describe('deleteUsingPagination', () => {
|
||||
it('should delete items using pagination', async () => {
|
||||
const workspaceId = 'workspace123';
|
||||
const batchSize = 10;
|
||||
const getterPaginated = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(['id1', 'id2'])
|
||||
.mockResolvedValueOnce([]);
|
||||
const deleter = jest.fn();
|
||||
const transactionManager = undefined;
|
||||
|
||||
await deleteUsingPagination(
|
||||
workspaceId,
|
||||
batchSize,
|
||||
getterPaginated,
|
||||
deleter,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
expect(getterPaginated).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
batchSize,
|
||||
0,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
expect(getterPaginated).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
batchSize,
|
||||
batchSize,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
expect(deleter).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
['id1', 'id2'],
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
expect(deleter).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,38 @@
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
export const deleteUsingPagination = async (
|
||||
workspaceId: string,
|
||||
batchSize: number,
|
||||
getterPaginated: (
|
||||
limit: number,
|
||||
offset: number,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
) => Promise<string[]>,
|
||||
deleter: (
|
||||
ids: string[],
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
) => Promise<void>,
|
||||
transactionManager?: EntityManager,
|
||||
) => {
|
||||
let offset = 0;
|
||||
let hasMoreData = true;
|
||||
|
||||
while (hasMoreData) {
|
||||
const idsToDelete = await getterPaginated(
|
||||
batchSize,
|
||||
offset,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
if (idsToDelete.length > 0) {
|
||||
await deleter(idsToDelete, workspaceId, transactionManager);
|
||||
} else {
|
||||
hasMoreData = false;
|
||||
}
|
||||
|
||||
offset += batchSize;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user