mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Do not notify about new messages in initial Gmail sync (#9673)
Signed-off-by: Artem Savchenko <armisav@gmail.com> Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
parent
d40eb99774
commit
c4f9953d7d
@@ -133,7 +133,7 @@ describe('SyncManager', () => {
|
||||
await syncManager.sync(userId, { noNotify: true }, userEmail)
|
||||
|
||||
// Assert
|
||||
expect(spyFullSync).toHaveBeenCalledWith(userId, userEmail)
|
||||
expect(spyFullSync).toHaveBeenCalledWith(userId, userEmail, undefined, { noNotify: true })
|
||||
})
|
||||
|
||||
it('should perform part sync when history exists', async () => {
|
||||
@@ -197,7 +197,7 @@ describe('SyncManager', () => {
|
||||
// Assert
|
||||
expect(spyGetHistory).toHaveBeenCalledWith(userId)
|
||||
expect(mockCtx.info).toHaveBeenCalledWith('No stored history ID found, performing full sync instead', { userId })
|
||||
expect(spyFullSync).toHaveBeenCalledWith(userId, userEmail)
|
||||
expect(spyFullSync).toHaveBeenCalledWith(userId, userEmail, undefined, undefined)
|
||||
})
|
||||
|
||||
it('should throw error when userEmail is undefined', async () => {
|
||||
|
||||
@@ -71,6 +71,8 @@ export class SyncManager {
|
||||
this.processedMessages = 0
|
||||
this.syncStartTime = new Date()
|
||||
|
||||
const syncOptions = await this.getSyncOptions(userId, options)
|
||||
|
||||
try {
|
||||
let pageToken: string | undefined
|
||||
let histories: GaxiosResponse<gmail_v1.Schema$ListHistoryResponse>
|
||||
@@ -91,7 +93,7 @@ export class SyncManager {
|
||||
error: err.message,
|
||||
historyId
|
||||
})
|
||||
void this.syncNewMessages(userId, userEmail)
|
||||
void this.syncNewMessages(userId, userEmail, syncOptions)
|
||||
return
|
||||
}
|
||||
const nextPageToken = histories.data.nextPageToken
|
||||
@@ -106,7 +108,7 @@ export class SyncManager {
|
||||
}
|
||||
try {
|
||||
const res = await this.getMessage(message.message.id)
|
||||
await this.messageManager.saveMessage(res, userEmail)
|
||||
await this.messageManager.saveMessage(res, userEmail, syncOptions)
|
||||
this.processedMessages++
|
||||
} catch (err) {
|
||||
this.ctx.error('Part sync message error', {
|
||||
@@ -143,6 +145,7 @@ export class SyncManager {
|
||||
})
|
||||
}
|
||||
}
|
||||
await this.stateManager.setLastSyncDate(userId, new Date())
|
||||
return
|
||||
} else {
|
||||
pageToken = nextPageToken
|
||||
@@ -160,7 +163,7 @@ export class SyncManager {
|
||||
}
|
||||
}
|
||||
|
||||
async fullSync (userId: PersonId, userEmail?: string, q?: string): Promise<void> {
|
||||
async fullSync (userId: PersonId, userEmail?: string, q?: string, options?: SyncOptions): Promise<void> {
|
||||
this.ctx.info('Start full sync', { workspaceUuid: this.workspace, userId, userEmail })
|
||||
if (userEmail === undefined) {
|
||||
throw new Error('Cannot sync without user email')
|
||||
@@ -171,6 +174,8 @@ export class SyncManager {
|
||||
this.processedMessages = 0
|
||||
this.syncStartTime = new Date()
|
||||
|
||||
const syncOptions = await this.getSyncOptions(userId, options)
|
||||
|
||||
try {
|
||||
// Get saved page token to continue from
|
||||
let pageToken: string | undefined = (await this.stateManager.getPageToken(userId)) ?? undefined
|
||||
@@ -205,7 +210,7 @@ export class SyncManager {
|
||||
try {
|
||||
const message = await this.getMessage(id)
|
||||
const historyId = message.data.historyId
|
||||
await this.messageManager.saveMessage(message, userEmail)
|
||||
await this.messageManager.saveMessage(message, userEmail, syncOptions)
|
||||
this.processedMessages++
|
||||
|
||||
if (historyId != null && q === undefined) {
|
||||
@@ -233,6 +238,7 @@ export class SyncManager {
|
||||
if (currentHistoryId != null) {
|
||||
await this.stateManager.setHistoryId(userId, currentHistoryId)
|
||||
}
|
||||
await this.stateManager.setLastSyncDate(userId, new Date())
|
||||
this.ctx.info('Full sync finished', { workspaceUuid: this.workspace, userId, userEmail })
|
||||
} catch (err) {
|
||||
if (this.isClosing) return
|
||||
@@ -245,6 +251,19 @@ export class SyncManager {
|
||||
}
|
||||
}
|
||||
|
||||
private async getSyncOptions (userId: PersonId, syncOptions?: SyncOptions): Promise<SyncOptions | undefined> {
|
||||
try {
|
||||
const isUpdateAfterFullSync = (await this.stateManager.getLastSyncDate(userId)) != null
|
||||
return {
|
||||
...(syncOptions ?? {}),
|
||||
noNotify: isUpdateAfterFullSync ? syncOptions?.noNotify ?? false : true
|
||||
}
|
||||
} catch (err) {
|
||||
this.ctx.error('Error getting last sync date', { workspace: this.workspace, userId, err })
|
||||
return syncOptions
|
||||
}
|
||||
}
|
||||
|
||||
private async getMessage (id: string): Promise<GaxiosResponse<gmail_v1.Schema$Message>> {
|
||||
await this.rateLimiter.take(5)
|
||||
return await this.gmail.messages.get({
|
||||
@@ -254,7 +273,7 @@ export class SyncManager {
|
||||
})
|
||||
}
|
||||
|
||||
async syncNewMessages (userId: PersonId, userEmail?: string): Promise<void> {
|
||||
async syncNewMessages (userId: PersonId, userEmail?: string, options?: SyncOptions): Promise<void> {
|
||||
this.ctx.info('Start sync new messages', { workspaceUuid: this.workspace, userId, userEmail })
|
||||
if (userEmail === undefined) {
|
||||
throw new Error('Cannot sync without user email')
|
||||
@@ -266,7 +285,7 @@ export class SyncManager {
|
||||
|
||||
if (storedHistoryId == null) {
|
||||
this.ctx.info('No stored history ID found, performing full sync instead', { userId })
|
||||
await this.fullSync(userId, userEmail)
|
||||
await this.fullSync(userId, userEmail, undefined, options)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -312,7 +331,7 @@ export class SyncManager {
|
||||
// If message has a history ID, check if it's newer than stored
|
||||
if (messageHistoryId != null) {
|
||||
if (BigInt(messageHistoryId) > BigInt(storedHistoryId)) {
|
||||
await this.messageManager.saveMessage(message, userEmail)
|
||||
await this.messageManager.saveMessage(message, userEmail, options)
|
||||
totalProcessedMessages++
|
||||
|
||||
// Track the maximum history ID found
|
||||
@@ -331,7 +350,7 @@ export class SyncManager {
|
||||
}
|
||||
} else {
|
||||
// Message without history ID, save it anyway
|
||||
await this.messageManager.saveMessage(message, userEmail)
|
||||
await this.messageManager.saveMessage(message, userEmail, options)
|
||||
totalProcessedMessages++
|
||||
}
|
||||
} catch (err: any) {
|
||||
@@ -401,7 +420,7 @@ export class SyncManager {
|
||||
await this.partSync(userId, userEmail, history.historyId, options)
|
||||
} else {
|
||||
this.ctx.info('Start full sync', { workspaceUuid: this.workspace, userId })
|
||||
await this.fullSync(userId, userEmail)
|
||||
await this.fullSync(userId, userEmail, undefined, options)
|
||||
}
|
||||
} catch (err) {
|
||||
if (this.isClosing) return
|
||||
|
||||
@@ -58,6 +58,17 @@ export class SyncStateManager {
|
||||
await this.keyValueClient.setValue(pageTokenKey, pageToken)
|
||||
}
|
||||
|
||||
async getLastSyncDate (userId: PersonId): Promise<Date | null> {
|
||||
const lastSyncKey = this.getLastSyncKey(userId)
|
||||
const lastSync = await this.keyValueClient.getValue<Date>(lastSyncKey)
|
||||
return lastSync != null ? new Date(lastSync) : null
|
||||
}
|
||||
|
||||
async setLastSyncDate (userId: PersonId, date: Date): Promise<void> {
|
||||
const lastSyncKey = this.getLastSyncKey(userId)
|
||||
await this.keyValueClient.setValue(lastSyncKey, date)
|
||||
}
|
||||
|
||||
private getHistoryKey (userId: PersonId): string {
|
||||
if (this.version === IntegrationVersion.V2) {
|
||||
return `history-v2:${this.workspace}:${userId}`
|
||||
@@ -71,4 +82,11 @@ export class SyncStateManager {
|
||||
}
|
||||
return `page-token:${this.workspace}:${userId}`
|
||||
}
|
||||
|
||||
private getLastSyncKey (userId: PersonId): string {
|
||||
if (this.version === IntegrationVersion.V2) {
|
||||
return `last-sync-date-v2:${this.workspace}:${userId}`
|
||||
}
|
||||
return `last-sync-date:${this.workspace}:${userId}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import { SyncOptions } from '@hcengineering/mail-common'
|
||||
import { type GaxiosResponse } from 'gaxios'
|
||||
import { gmail_v1 } from 'googleapis'
|
||||
|
||||
@@ -22,5 +23,5 @@ export interface History {
|
||||
}
|
||||
|
||||
export interface IMessageManager {
|
||||
saveMessage: (message: GaxiosResponse<gmail_v1.Schema$Message>, me: string) => Promise<void>
|
||||
saveMessage: (message: GaxiosResponse<gmail_v1.Schema$Message>, me: string, options?: SyncOptions) => Promise<void>
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import { type Channel } from '../../types'
|
||||
import { AttachmentHandler } from '../attachments'
|
||||
import { decode64 } from '../../base64'
|
||||
import { diffAttributes } from '../../utils'
|
||||
import { SyncOptions } from '@hcengineering/mail-common'
|
||||
|
||||
const EMAIL_REGEX =
|
||||
/(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/
|
||||
@@ -65,7 +66,11 @@ export class MessageManagerV1 implements IMessageManager {
|
||||
return result
|
||||
}
|
||||
|
||||
async saveMessage (message: GaxiosResponse<gmail_v1.Schema$Message>, me: string): Promise<void> {
|
||||
async saveMessage (
|
||||
message: GaxiosResponse<gmail_v1.Schema$Message>,
|
||||
me: string,
|
||||
options?: SyncOptions
|
||||
): Promise<void> {
|
||||
const res = convertMessage(message, me)
|
||||
const channels = this.findChannels(res)
|
||||
if (channels.length === 0) return
|
||||
|
||||
@@ -26,7 +26,8 @@ import {
|
||||
MailRecipient,
|
||||
getMessageExtra,
|
||||
HulyMailHeader,
|
||||
HulyMessageIdHeader
|
||||
HulyMessageIdHeader,
|
||||
SyncOptions
|
||||
} from '@hcengineering/mail-common'
|
||||
import { type KeyValueClient } from '@hcengineering/kvs-client'
|
||||
import { AccountClient, isWorkspaceLoginInfo, WorkspaceLoginInfo } from '@hcengineering/account-client'
|
||||
@@ -49,7 +50,11 @@ export class MessageManagerV2 implements IMessageManager {
|
||||
private readonly recipient: MailRecipient
|
||||
) {}
|
||||
|
||||
async saveMessage (message: GaxiosResponse<gmail_v1.Schema$Message>, me: string): Promise<void> {
|
||||
async saveMessage (
|
||||
message: GaxiosResponse<gmail_v1.Schema$Message>,
|
||||
me: string,
|
||||
options?: SyncOptions
|
||||
): Promise<void> {
|
||||
if (isHulyMessage(message.data.payload)) {
|
||||
this.ctx.info('Skipping Huly message', { mailId: message.data.id, me })
|
||||
return
|
||||
@@ -76,7 +81,8 @@ export class MessageManagerV2 implements IMessageManager {
|
||||
this.wsInfo,
|
||||
res,
|
||||
attachments,
|
||||
[this.recipient]
|
||||
[this.recipient],
|
||||
options
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user