Add receipt metrics and logs to connected account sync webhooks (#22853)
Webhook deliveries from Google and Microsoft were invisible at the app level: successful notifications produced no logs and no metrics, so webhook-triggered syncs could not be told apart from cron polling. Add two counters, connected-account-sync-webhook/received/messaging and /received/calendar, mirroring the sync-job metric umbrellas, and log a line whenever a notification triggers a sync. Unmatched subscriptions keep their existing warn logs. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22853?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -53,4 +53,6 @@ export enum MetricsKeys {
|
||||
AiChatTurnCompleted = 'ai-chat/turn-completed',
|
||||
AiChatTurnFailed = 'ai-chat/turn-failed',
|
||||
WorkspaceMetadataCacheLocalEviction = 'workspace-metadata-cache/local-eviction',
|
||||
ConnectedAccountSyncWebhookReceivedMessaging = 'connected-account-sync-webhook/received/messaging',
|
||||
ConnectedAccountSyncWebhookReceivedCalendar = 'connected-account-sync-webhook/received/calendar',
|
||||
}
|
||||
|
||||
+19
-1
@@ -1,6 +1,6 @@
|
||||
import { timingSafeEqual } from 'crypto';
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
@@ -8,6 +8,8 @@ import { WebhookSubscriptionStatus } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
|
||||
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
|
||||
import { WebhookSyncTriggerService } from 'src/modules/connected-account/webhook-subscription-manager/services/webhook-sync-trigger.service';
|
||||
import {
|
||||
@@ -21,13 +23,21 @@ const GOOGLE_CALENDAR_SYNC_RESOURCE_STATE = 'sync';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleCalendarNotificationHandler implements WebhookNotificationHandler<GoogleCalendarChannelNotification> {
|
||||
private readonly logger = new Logger(GoogleCalendarNotificationHandler.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(CalendarChannelEntity)
|
||||
private readonly calendarChannelRepository: Repository<CalendarChannelEntity>,
|
||||
private readonly webhookSyncTriggerService: WebhookSyncTriggerService,
|
||||
private readonly metricsService: MetricsService,
|
||||
) {}
|
||||
|
||||
async handle(request: GoogleCalendarChannelNotification): Promise<void> {
|
||||
this.metricsService.incrementCounterBy({
|
||||
key: MetricsKeys.ConnectedAccountSyncWebhookReceivedCalendar,
|
||||
amount: 1,
|
||||
});
|
||||
|
||||
if (request.resourceState === GOOGLE_CALENDAR_SYNC_RESOURCE_STATE) {
|
||||
return;
|
||||
}
|
||||
@@ -44,6 +54,10 @@ export class GoogleCalendarNotificationHandler implements WebhookNotificationHan
|
||||
});
|
||||
|
||||
if (!isDefined(calendarChannel)) {
|
||||
this.logger.warn(
|
||||
`No calendar channel found for Google channel ${request.channelId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -72,5 +86,9 @@ export class GoogleCalendarNotificationHandler implements WebhookNotificationHan
|
||||
calendarChannel.id,
|
||||
calendarChannel.workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Triggered calendar sync for calendar channel ${calendarChannel.id} from Google notification`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+23
-1
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
@@ -31,8 +33,11 @@ export type GoogleMessagingNotificationRequest = {
|
||||
|
||||
@Injectable()
|
||||
export class GoogleMessagingNotificationHandler implements WebhookNotificationHandler<GoogleMessagingNotificationRequest> {
|
||||
private readonly logger = new Logger(GoogleMessagingNotificationHandler.name);
|
||||
|
||||
constructor(
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly metricsService: MetricsService,
|
||||
@InjectRepository(ConnectedAccountEntity)
|
||||
private readonly connectedAccountRepository: Repository<ConnectedAccountEntity>,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
@@ -43,6 +48,11 @@ export class GoogleMessagingNotificationHandler implements WebhookNotificationHa
|
||||
async handle(request: GoogleMessagingNotificationRequest): Promise<void> {
|
||||
await this.verify(request.authorizationHeader);
|
||||
|
||||
this.metricsService.incrementCounterBy({
|
||||
key: MetricsKeys.ConnectedAccountSyncWebhookReceivedMessaging,
|
||||
amount: 1,
|
||||
});
|
||||
|
||||
const decodedData = this.decodeMessageData(request.body);
|
||||
|
||||
if (!isDefined(decodedData)) {
|
||||
@@ -61,6 +71,10 @@ export class GoogleMessagingNotificationHandler implements WebhookNotificationHa
|
||||
);
|
||||
|
||||
if (connectedAccountIds.length === 0) {
|
||||
this.logger.warn(
|
||||
'No Google connected account matches Gmail notification',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -71,12 +85,20 @@ export class GoogleMessagingNotificationHandler implements WebhookNotificationHa
|
||||
},
|
||||
});
|
||||
|
||||
if (messageChannels.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await this.webhookSyncTriggerService.triggerMessagingSync(
|
||||
messageChannel.id,
|
||||
messageChannel.workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Triggered messaging sync for ${messageChannels.length} message channels from Gmail notification`,
|
||||
);
|
||||
}
|
||||
|
||||
private async verify(authorizationHeader: string | undefined): Promise<void> {
|
||||
|
||||
+2
@@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
|
||||
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
||||
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
@@ -12,6 +13,7 @@ import { GoogleMessagingNotificationHandler } from 'src/modules/connected-accoun
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MetricsModule,
|
||||
TwentyConfigModule,
|
||||
WebhookSubscriptionModule,
|
||||
TypeOrmModule.forFeature([
|
||||
|
||||
+14
@@ -7,6 +7,8 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
|
||||
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
|
||||
import { CalendarWebhookSubscriptionService } from 'src/modules/connected-account/webhook-subscription-manager/services/calendar-webhook-subscription.service';
|
||||
import { WebhookSyncTriggerService } from 'src/modules/connected-account/webhook-subscription-manager/services/webhook-sync-trigger.service';
|
||||
@@ -26,9 +28,17 @@ export class MicrosoftCalendarNotificationHandler implements WebhookNotification
|
||||
private readonly calendarChannelRepository: Repository<CalendarChannelEntity>,
|
||||
private readonly calendarWebhookSubscriptionService: CalendarWebhookSubscriptionService,
|
||||
private readonly webhookSyncTriggerService: WebhookSyncTriggerService,
|
||||
private readonly metricsService: MetricsService,
|
||||
) {}
|
||||
|
||||
async handle(notifications: MicrosoftGraphNotification[]): Promise<void> {
|
||||
if (notifications.length > 0) {
|
||||
this.metricsService.incrementCounterBy({
|
||||
key: MetricsKeys.ConnectedAccountSyncWebhookReceivedCalendar,
|
||||
amount: notifications.length,
|
||||
});
|
||||
}
|
||||
|
||||
const subscriptionIds = notifications
|
||||
.map((notification) => notification.subscriptionId)
|
||||
.filter(isNonEmptyString);
|
||||
@@ -96,6 +106,10 @@ export class MicrosoftCalendarNotificationHandler implements WebhookNotification
|
||||
calendarChannel.id,
|
||||
calendarChannel.workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Triggered calendar sync for calendar channel ${calendarChannel.id} from Microsoft notification`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
@@ -7,6 +7,8 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MessagingWebhookSubscriptionService } from 'src/modules/connected-account/webhook-subscription-manager/services/messaging-webhook-subscription.service';
|
||||
import { WebhookSyncTriggerService } from 'src/modules/connected-account/webhook-subscription-manager/services/webhook-sync-trigger.service';
|
||||
@@ -26,9 +28,17 @@ export class MicrosoftMessagingNotificationHandler implements WebhookNotificatio
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
private readonly messagingWebhookSubscriptionService: MessagingWebhookSubscriptionService,
|
||||
private readonly webhookSyncTriggerService: WebhookSyncTriggerService,
|
||||
private readonly metricsService: MetricsService,
|
||||
) {}
|
||||
|
||||
async handle(notifications: MicrosoftGraphNotification[]): Promise<void> {
|
||||
if (notifications.length > 0) {
|
||||
this.metricsService.incrementCounterBy({
|
||||
key: MetricsKeys.ConnectedAccountSyncWebhookReceivedMessaging,
|
||||
amount: notifications.length,
|
||||
});
|
||||
}
|
||||
|
||||
const subscriptionIds = notifications
|
||||
.map((notification) => notification.subscriptionId)
|
||||
.filter(isNonEmptyString);
|
||||
@@ -95,6 +105,10 @@ export class MicrosoftMessagingNotificationHandler implements WebhookNotificatio
|
||||
messageChannel.id,
|
||||
messageChannel.workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Triggered messaging sync for message channel ${messageChannel.id} from Microsoft notification`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
|
||||
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
||||
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
@@ -11,6 +12,7 @@ import { MicrosoftMessagingNotificationHandler } from 'src/modules/connected-acc
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MetricsModule,
|
||||
TwentyConfigModule,
|
||||
WebhookSubscriptionModule,
|
||||
TypeOrmModule.forFeature([MessageChannelEntity, CalendarChannelEntity]),
|
||||
|
||||
Reference in New Issue
Block a user