From 2ec86ac72949a5aced885bf7ec2e8d34e97cab7a Mon Sep 17 00:00:00 2001 From: Tomi Turtiainen <10324676+tomi@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:16:47 +0300 Subject: [PATCH 1/2] fix(core): Flush instance stopped event immediately Rudderstack flushes events after 20 events have been batched or 10 seconds have elapsed since the last flush. Since we were waiting for the 'User instance stopped' event to be sent before calling flush, we might have waited up to 10 seconds before the event got sent. Instead call the flush right away to send the stopped and any other remaining events immediately. --- packages/cli/src/telemetry/index.ts | 43 ++++++++++++++++------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/cli/src/telemetry/index.ts b/packages/cli/src/telemetry/index.ts index f63caf9b2c7b6..82949f3a37099 100644 --- a/packages/cli/src/telemetry/index.ts +++ b/packages/cli/src/telemetry/index.ts @@ -171,8 +171,13 @@ export class Telemetry { async trackN8nStop(): Promise { clearInterval(this.pulseIntervalReference); - await this.track('User instance stopped'); - void Promise.all([this.postHog.stop(), this.rudderStack?.flush()]); + + // Send the stopped event and make sure it gets flushed right away + await Promise.all([ + this.track('User instance stopped'), + this.postHog.stop(), + this.rudderStack?.flush(), + ]); } async identify(traits?: { @@ -201,28 +206,28 @@ export class Telemetry { ): Promise { const { instanceId } = this.instanceSettings; return await new Promise((resolve) => { - if (this.rudderStack) { - const { user_id } = properties; - const updatedProperties = { - ...properties, - instance_id: instanceId, - version_cli: N8N_VERSION, - }; + if (!this.rudderStack) { + return resolve(); + } - const payload = { - userId: `${instanceId}${user_id ? `#${user_id}` : ''}`, - event: eventName, - properties: updatedProperties, - }; + const { user_id } = properties; + const updatedProperties = { + ...properties, + instance_id: instanceId, + version_cli: N8N_VERSION, + }; - if (withPostHog) { - this.postHog?.track(payload); - } + const payload = { + userId: `${instanceId}${user_id ? `#${user_id}` : ''}`, + event: eventName, + properties: updatedProperties, + }; - return this.rudderStack.track(payload, resolve); + if (withPostHog) { + this.postHog?.track(payload); } - return resolve(); + return this.rudderStack.track(payload, resolve); }); } From 44d53f20286827747b7d72ce2112d377c01b9c32 Mon Sep 17 00:00:00 2001 From: Tomi Turtiainen <10324676+tomi@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:14:41 +0300 Subject: [PATCH 2/2] refactor(core): Make telemetry track & identify synchronous We are not actually waiting for the track & identify events anywhere, so it doesn't make sense to have the method async. --- packages/cli/src/InternalHooks.ts | 171 +++++++----------- .../handlers/executions/executions.handler.ts | 4 +- .../v1/handlers/users/users.handler.ee.ts | 4 +- .../handlers/workflows/workflows.handler.ts | 6 +- .../email/UserManagementMailer.ts | 8 +- packages/cli/src/auth/methods/ldap.ts | 2 +- .../concurrency-control.service.ts | 2 +- .../cli/src/controllers/auth.controller.ts | 2 +- .../communityPackages.controller.ts | 2 - .../src/controllers/invitation.controller.ts | 2 +- packages/cli/src/controllers/me.controller.ts | 6 +- .../cli/src/controllers/owner.controller.ts | 2 +- .../controllers/passwordReset.controller.ts | 12 +- .../cli/src/controllers/users.controller.ts | 4 +- packages/cli/src/services/frontend.service.ts | 2 +- packages/cli/src/services/user.service.ts | 6 +- packages/cli/src/telemetry/index.ts | 121 ++++++------- .../telemetry-event-relay.service.ts | 64 ++++--- .../cli/src/workflows/workflow.service.ts | 2 +- .../cli/src/workflows/workflows.controller.ts | 4 +- packages/cli/test/unit/Telemetry.test.ts | 76 ++++---- 21 files changed, 223 insertions(+), 279 deletions(-) diff --git a/packages/cli/src/InternalHooks.ts b/packages/cli/src/InternalHooks.ts index e6418743d7b1a..dedfb46b0cb3f 100644 --- a/packages/cli/src/InternalHooks.ts +++ b/packages/cli/src/InternalHooks.ts @@ -43,13 +43,11 @@ export class InternalHooks { private readonly projectRelationRepository: ProjectRelationRepository, private readonly _eventBus: MessageEventBus, // needed until we decouple telemetry ) { - workflowStatisticsService.on( - 'telemetry.onFirstProductionWorkflowSuccess', - async (metrics) => await this.onFirstProductionWorkflowSuccess(metrics), + workflowStatisticsService.on('telemetry.onFirstProductionWorkflowSuccess', (metrics) => + this.onFirstProductionWorkflowSuccess(metrics), ); - workflowStatisticsService.on( - 'telemetry.onFirstWorkflowDataLoad', - async (metrics) => await this.onFirstWorkflowDataLoad(metrics), + workflowStatisticsService.on('telemetry.onFirstWorkflowDataLoad', (metrics) => + this.onFirstWorkflowDataLoad(metrics), ); } @@ -57,35 +55,29 @@ export class InternalHooks { await this.telemetry.init(); } - async onFrontendSettingsAPI(pushRef?: string): Promise { - return await this.telemetry.track('Session started', { session_id: pushRef }); + onFrontendSettingsAPI(pushRef?: string): void { + this.telemetry.track('Session started', { session_id: pushRef }); } - async onPersonalizationSurveySubmitted( - userId: string, - answers: Record, - ): Promise { + onPersonalizationSurveySubmitted(userId: string, answers: Record): void { const camelCaseKeys = Object.keys(answers); const personalizationSurveyData = { user_id: userId } as Record; camelCaseKeys.forEach((camelCaseKey) => { personalizationSurveyData[snakeCase(camelCaseKey)] = answers[camelCaseKey]; }); - return await this.telemetry.track( - 'User responded to personalization questions', - personalizationSurveyData, - ); + this.telemetry.track('User responded to personalization questions', personalizationSurveyData); } - async onWorkflowCreated( + onWorkflowCreated( user: User, workflow: IWorkflowBase, project: Project, publicApi: boolean, - ): Promise { + ): void { const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes); - void this.telemetry.track('User created workflow', { + this.telemetry.track('User created workflow', { user_id: user.id, workflow_id: workflow.id, node_graph_string: JSON.stringify(nodeGraph), @@ -95,8 +87,8 @@ export class InternalHooks { }); } - async onWorkflowDeleted(user: User, workflowId: string, publicApi: boolean): Promise { - void this.telemetry.track('User deleted workflow', { + onWorkflowDeleted(user: User, workflowId: string, publicApi: boolean): void { + this.telemetry.track('User deleted workflow', { user_id: user.id, workflow_id: workflowId, public_api: publicApi, @@ -136,7 +128,7 @@ export class InternalHooks { (note) => note.overlapping, ).length; - void this.telemetry.track('User saved workflow', { + this.telemetry.track('User saved workflow', { user_id: user.id, workflow_id: workflow.id, node_graph_string: JSON.stringify(nodeGraph), @@ -155,7 +147,7 @@ export class InternalHooks { workflow: IWorkflowBase, runData?: IRun, userId?: string, - ): Promise { + ) { if (!workflow.id) { return; } @@ -165,8 +157,6 @@ export class InternalHooks { return; } - const promises = []; - const telemetryProperties: IExecutionTrackProperties = { workflow_id: workflow.id, is_manual: false, @@ -270,7 +260,7 @@ export class InternalHooks { node_id: nodeGraphResult.nameIndices[runData.data.startData?.destinationNode], }; - promises.push(this.telemetry.track('Manual node exec finished', telemetryPayload)); + this.telemetry.track('Manual node exec finished', telemetryPayload); } else { nodeGraphResult.webhookNodeNames.forEach((name: string) => { const execJson = runData.data.resultData.runData[name]?.[0]?.data?.main?.[0]?.[0] @@ -282,56 +272,52 @@ export class InternalHooks { } }); - promises.push( - this.telemetry.track('Manual workflow exec finished', manualExecEventProperties), - ); + this.telemetry.track('Manual workflow exec finished', manualExecEventProperties); } } } - void Promise.all([...promises, this.telemetry.trackWorkflowExecution(telemetryProperties)]); + this.telemetry.trackWorkflowExecution(telemetryProperties); } - async onWorkflowSharingUpdate(workflowId: string, userId: string, userList: string[]) { + onWorkflowSharingUpdate(workflowId: string, userId: string, userList: string[]) { const properties: ITelemetryTrackProperties = { workflow_id: workflowId, user_id_sharer: userId, user_id_list: userList, }; - return await this.telemetry.track('User updated workflow sharing', properties); + this.telemetry.track('User updated workflow sharing', properties); } async onN8nStop(): Promise { const timeoutPromise = new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, 3000); + setTimeout(resolve, 3000); }); return await Promise.race([timeoutPromise, this.telemetry.trackN8nStop()]); } - async onUserDeletion(userDeletionData: { + onUserDeletion(userDeletionData: { user: User; telemetryData: ITelemetryUserDeletionData; publicApi: boolean; - }): Promise { - void this.telemetry.track('User deleted user', { + }) { + this.telemetry.track('User deleted user', { ...userDeletionData.telemetryData, user_id: userDeletionData.user.id, public_api: userDeletionData.publicApi, }); } - async onUserInvite(userInviteData: { + onUserInvite(userInviteData: { user: User; target_user_id: string[]; public_api: boolean; email_sent: boolean; invitee_role: string; - }): Promise { - void this.telemetry.track('User invited new user', { + }) { + this.telemetry.track('User invited new user', { user_id: userInviteData.user.id, target_user_id: userInviteData.target_user_id, public_api: userInviteData.public_api, @@ -340,7 +326,7 @@ export class InternalHooks { }); } - async onUserRoleChange(userRoleChangeData: { + onUserRoleChange(userRoleChangeData: { user: User; target_user_id: string; public_api: boolean; @@ -348,74 +334,53 @@ export class InternalHooks { }) { const { user, ...rest } = userRoleChangeData; - void this.telemetry.track('User changed role', { user_id: user.id, ...rest }); + this.telemetry.track('User changed role', { user_id: user.id, ...rest }); } - async onUserRetrievedUser(userRetrievedData: { - user_id: string; - public_api: boolean; - }): Promise { - return await this.telemetry.track('User retrieved user', userRetrievedData); + onUserRetrievedUser(userRetrievedData: { user_id: string; public_api: boolean }) { + this.telemetry.track('User retrieved user', userRetrievedData); } - async onUserRetrievedAllUsers(userRetrievedData: { - user_id: string; - public_api: boolean; - }): Promise { - return await this.telemetry.track('User retrieved all users', userRetrievedData); + onUserRetrievedAllUsers(userRetrievedData: { user_id: string; public_api: boolean }) { + this.telemetry.track('User retrieved all users', userRetrievedData); } - async onUserRetrievedExecution(userRetrievedData: { - user_id: string; - public_api: boolean; - }): Promise { - return await this.telemetry.track('User retrieved execution', userRetrievedData); + onUserRetrievedExecution(userRetrievedData: { user_id: string; public_api: boolean }) { + this.telemetry.track('User retrieved execution', userRetrievedData); } - async onUserRetrievedAllExecutions(userRetrievedData: { - user_id: string; - public_api: boolean; - }): Promise { - return await this.telemetry.track('User retrieved all executions', userRetrievedData); + onUserRetrievedAllExecutions(userRetrievedData: { user_id: string; public_api: boolean }) { + this.telemetry.track('User retrieved all executions', userRetrievedData); } - async onUserRetrievedWorkflow(userRetrievedData: { - user_id: string; - public_api: boolean; - }): Promise { - return await this.telemetry.track('User retrieved workflow', userRetrievedData); + onUserRetrievedWorkflow(userRetrievedData: { user_id: string; public_api: boolean }) { + this.telemetry.track('User retrieved workflow', userRetrievedData); } - async onUserRetrievedAllWorkflows(userRetrievedData: { - user_id: string; - public_api: boolean; - }): Promise { - return await this.telemetry.track('User retrieved all workflows', userRetrievedData); + onUserRetrievedAllWorkflows(userRetrievedData: { user_id: string; public_api: boolean }) { + this.telemetry.track('User retrieved all workflows', userRetrievedData); } - async onUserUpdate(userUpdateData: { user: User; fields_changed: string[] }): Promise { - void this.telemetry.track('User changed personal settings', { + onUserUpdate(userUpdateData: { user: User; fields_changed: string[] }) { + this.telemetry.track('User changed personal settings', { user_id: userUpdateData.user.id, fields_changed: userUpdateData.fields_changed, }); } - async onUserInviteEmailClick(userInviteClickData: { - inviter: User; - invitee: User; - }): Promise { - void this.telemetry.track('User clicked invite link from email', { + onUserInviteEmailClick(userInviteClickData: { inviter: User; invitee: User }) { + this.telemetry.track('User clicked invite link from email', { user_id: userInviteClickData.invitee.id, }); } - async onUserPasswordResetEmailClick(userPasswordResetData: { user: User }): Promise { - void this.telemetry.track('User clicked password reset link from email', { + onUserPasswordResetEmailClick(userPasswordResetData: { user: User }) { + this.telemetry.track('User clicked password reset link from email', { user_id: userPasswordResetData.user.id, }); } - async onUserTransactionalEmail(userTransactionalEmailData: { + onUserTransactionalEmail(userTransactionalEmailData: { user_id: string; message_type: | 'Reset password' @@ -424,37 +389,34 @@ export class InternalHooks { | 'Workflow shared' | 'Credentials shared'; public_api: boolean; - }): Promise { - return await this.telemetry.track( - 'Instance sent transactional email to user', - userTransactionalEmailData, - ); + }) { + this.telemetry.track('Instance sent transactional email to user', userTransactionalEmailData); } - async onUserPasswordResetRequestClick(userPasswordResetData: { user: User }): Promise { - void this.telemetry.track('User requested password reset while logged out', { + onUserPasswordResetRequestClick(userPasswordResetData: { user: User }) { + this.telemetry.track('User requested password reset while logged out', { user_id: userPasswordResetData.user.id, }); } - async onInstanceOwnerSetup(instanceOwnerSetupData: { user_id: string }): Promise { - return await this.telemetry.track('Owner finished instance setup', instanceOwnerSetupData); + onInstanceOwnerSetup(instanceOwnerSetupData: { user_id: string }) { + this.telemetry.track('Owner finished instance setup', instanceOwnerSetupData); } - async onUserSignup( + onUserSignup( user: User, userSignupData: { user_type: AuthProviderType; was_disabled_ldap_user: boolean; }, - ): Promise { - void this.telemetry.track('User signed up', { + ) { + this.telemetry.track('User signed up', { user_id: user.id, ...userSignupData, }); } - async onEmailFailed(failedEmailData: { + onEmailFailed(failedEmailData: { user: User; message_type: | 'Reset password' @@ -463,8 +425,8 @@ export class InternalHooks { | 'Workflow shared' | 'Credentials shared'; public_api: boolean; - }): Promise { - void this.telemetry.track('Instance failed to send transactional email to user', { + }) { + this.telemetry.track('Instance failed to send transactional email to user', { user_id: failedEmailData.user.id, }); } @@ -472,21 +434,18 @@ export class InternalHooks { /* * Execution Statistics */ - async onFirstProductionWorkflowSuccess(data: { - user_id: string; - workflow_id: string; - }): Promise { - return await this.telemetry.track('Workflow first prod success', data); + onFirstProductionWorkflowSuccess(data: { user_id: string; workflow_id: string }) { + this.telemetry.track('Workflow first prod success', data); } - async onFirstWorkflowDataLoad(data: { + onFirstWorkflowDataLoad(data: { user_id: string; workflow_id: string; node_type: string; node_id: string; credential_type?: string; credential_id?: string; - }): Promise { - return await this.telemetry.track('Workflow first data fetched', data); + }) { + this.telemetry.track('Workflow first data fetched', data); } } diff --git a/packages/cli/src/PublicApi/v1/handlers/executions/executions.handler.ts b/packages/cli/src/PublicApi/v1/handlers/executions/executions.handler.ts index cd936d1d1cec1..16ca8093cb325 100644 --- a/packages/cli/src/PublicApi/v1/handlers/executions/executions.handler.ts +++ b/packages/cli/src/PublicApi/v1/handlers/executions/executions.handler.ts @@ -78,7 +78,7 @@ export = { return res.status(404).json({ message: 'Not Found' }); } - void Container.get(InternalHooks).onUserRetrievedExecution({ + Container.get(InternalHooks).onUserRetrievedExecution({ user_id: req.user.id, public_api: true, }); @@ -129,7 +129,7 @@ export = { const count = await Container.get(ExecutionRepository).getExecutionsCountForPublicApi(filters); - void Container.get(InternalHooks).onUserRetrievedAllExecutions({ + Container.get(InternalHooks).onUserRetrievedAllExecutions({ user_id: req.user.id, public_api: true, }); diff --git a/packages/cli/src/PublicApi/v1/handlers/users/users.handler.ee.ts b/packages/cli/src/PublicApi/v1/handlers/users/users.handler.ee.ts index 96b2d57239c57..0df22ec4aa9ef 100644 --- a/packages/cli/src/PublicApi/v1/handlers/users/users.handler.ee.ts +++ b/packages/cli/src/PublicApi/v1/handlers/users/users.handler.ee.ts @@ -33,7 +33,7 @@ export = { public_api: true, }; - void Container.get(InternalHooks).onUserRetrievedUser(telemetryData); + Container.get(InternalHooks).onUserRetrievedUser(telemetryData); return res.json(clean(user, { includeRole })); }, @@ -56,7 +56,7 @@ export = { public_api: true, }; - void Container.get(InternalHooks).onUserRetrievedAllUsers(telemetryData); + Container.get(InternalHooks).onUserRetrievedAllUsers(telemetryData); return res.json({ data: clean(users, { includeRole }), diff --git a/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts b/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts index 943d53764daf3..5139cb686fd60 100644 --- a/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts +++ b/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts @@ -58,7 +58,7 @@ export = { ); await Container.get(ExternalHooks).run('workflow.afterCreate', [createdWorkflow]); - void Container.get(InternalHooks).onWorkflowCreated(req.user, createdWorkflow, project, true); + Container.get(InternalHooks).onWorkflowCreated(req.user, createdWorkflow, project, true); Container.get(EventService).emit('workflow-created', { workflow: createdWorkflow, user: req.user, @@ -101,7 +101,7 @@ export = { return res.status(404).json({ message: 'Not Found' }); } - void Container.get(InternalHooks).onUserRetrievedWorkflow({ + Container.get(InternalHooks).onUserRetrievedWorkflow({ user_id: req.user.id, public_api: true, }); @@ -163,7 +163,7 @@ export = { ...(!config.getEnv('workflowTagsDisabled') && { relations: ['tags'] }), }); - void Container.get(InternalHooks).onUserRetrievedAllWorkflows({ + Container.get(InternalHooks).onUserRetrievedAllWorkflows({ user_id: req.user.id, public_api: true, }); diff --git a/packages/cli/src/UserManagement/email/UserManagementMailer.ts b/packages/cli/src/UserManagement/email/UserManagementMailer.ts index 531ce24cd67c4..6d60c3b4907e1 100644 --- a/packages/cli/src/UserManagement/email/UserManagementMailer.ts +++ b/packages/cli/src/UserManagement/email/UserManagementMailer.ts @@ -112,7 +112,7 @@ export class UserManagementMailer { this.logger.info('Sent workflow shared email successfully', { sharerId: sharer.id }); - void Container.get(InternalHooks).onUserTransactionalEmail({ + Container.get(InternalHooks).onUserTransactionalEmail({ user_id: sharer.id, message_type: 'Workflow shared', public_api: false, @@ -120,7 +120,7 @@ export class UserManagementMailer { return result; } catch (e) { - void Container.get(InternalHooks).onEmailFailed({ + Container.get(InternalHooks).onEmailFailed({ user: sharer, message_type: 'Workflow shared', public_api: false, @@ -171,7 +171,7 @@ export class UserManagementMailer { this.logger.info('Sent credentials shared email successfully', { sharerId: sharer.id }); - void Container.get(InternalHooks).onUserTransactionalEmail({ + Container.get(InternalHooks).onUserTransactionalEmail({ user_id: sharer.id, message_type: 'Credentials shared', public_api: false, @@ -179,7 +179,7 @@ export class UserManagementMailer { return result; } catch (e) { - void Container.get(InternalHooks).onEmailFailed({ + Container.get(InternalHooks).onEmailFailed({ user: sharer, message_type: 'Credentials shared', public_api: false, diff --git a/packages/cli/src/auth/methods/ldap.ts b/packages/cli/src/auth/methods/ldap.ts index 09e8f38c87a03..c8946aec9321e 100644 --- a/packages/cli/src/auth/methods/ldap.ts +++ b/packages/cli/src/auth/methods/ldap.ts @@ -51,7 +51,7 @@ export const handleLdapLogin = async ( await updateLdapUserOnLocalDb(identity, ldapAttributesValues); } else { const user = await createLdapUserOnLocalDb(ldapAttributesValues, ldapId); - void Container.get(InternalHooks).onUserSignup(user, { + Container.get(InternalHooks).onUserSignup(user, { user_type: 'ldap', was_disabled_ldap_user: false, }); diff --git a/packages/cli/src/concurrency/concurrency-control.service.ts b/packages/cli/src/concurrency/concurrency-control.service.ts index cf249c51722eb..f3268b295b697 100644 --- a/packages/cli/src/concurrency/concurrency-control.service.ts +++ b/packages/cli/src/concurrency/concurrency-control.service.ts @@ -55,7 +55,7 @@ export class ConcurrencyControlService { this.productionQueue.on('concurrency-check', ({ capacity }: { capacity: number }) => { if (this.shouldReport(capacity)) { - void this.telemetry.track('User hit concurrency limit', { + this.telemetry.track('User hit concurrency limit', { threshold: CLOUD_TEMP_PRODUCTION_LIMIT - capacity, }); } diff --git a/packages/cli/src/controllers/auth.controller.ts b/packages/cli/src/controllers/auth.controller.ts index 7994221d9db6c..7711d95177e0f 100644 --- a/packages/cli/src/controllers/auth.controller.ts +++ b/packages/cli/src/controllers/auth.controller.ts @@ -179,7 +179,7 @@ export class AuthController { throw new BadRequestError('Invalid request'); } - void this.internalHooks.onUserInviteEmailClick({ inviter, invitee }); + this.internalHooks.onUserInviteEmailClick({ inviter, invitee }); this.eventService.emit('user-invite-email-click', { inviter, invitee }); const { firstName, lastName } = inviter; diff --git a/packages/cli/src/controllers/communityPackages.controller.ts b/packages/cli/src/controllers/communityPackages.controller.ts index b37d3df249ce4..1860e1df86652 100644 --- a/packages/cli/src/controllers/communityPackages.controller.ts +++ b/packages/cli/src/controllers/communityPackages.controller.ts @@ -9,7 +9,6 @@ import { Delete, Get, Middleware, Patch, Post, RestController, GlobalScope } fro import { NodeRequest } from '@/requests'; import type { InstalledPackages } from '@db/entities/InstalledPackages'; import type { CommunityPackages } from '@/Interfaces'; -import { InternalHooks } from '@/InternalHooks'; import { Push } from '@/push'; import { CommunityPackagesService } from '@/services/communityPackages.service'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; @@ -37,7 +36,6 @@ export function isNpmError(error: unknown): error is { code: number; stdout: str export class CommunityPackagesController { constructor( private readonly push: Push, - private readonly internalHooks: InternalHooks, private readonly communityPackagesService: CommunityPackagesService, private readonly eventService: EventService, ) {} diff --git a/packages/cli/src/controllers/invitation.controller.ts b/packages/cli/src/controllers/invitation.controller.ts index d89d077ae61b0..c32bf543002fd 100644 --- a/packages/cli/src/controllers/invitation.controller.ts +++ b/packages/cli/src/controllers/invitation.controller.ts @@ -168,7 +168,7 @@ export class InvitationController { this.authService.issueCookie(res, updatedUser, req.browserId); - void this.internalHooks.onUserSignup(updatedUser, { + this.internalHooks.onUserSignup(updatedUser, { user_type: 'email', was_disabled_ldap_user: false, }); diff --git a/packages/cli/src/controllers/me.controller.ts b/packages/cli/src/controllers/me.controller.ts index 62a2e101ff27d..3f9366b441260 100644 --- a/packages/cli/src/controllers/me.controller.ts +++ b/packages/cli/src/controllers/me.controller.ts @@ -101,7 +101,7 @@ export class MeController { this.authService.issueCookie(res, user, req.browserId); const fieldsChanged = Object.keys(payload); - void this.internalHooks.onUserUpdate({ user, fields_changed: fieldsChanged }); + this.internalHooks.onUserUpdate({ user, fields_changed: fieldsChanged }); this.eventService.emit('user-updated', { user, fieldsChanged }); const publicUser = await this.userService.toPublic(user); @@ -151,7 +151,7 @@ export class MeController { this.authService.issueCookie(res, updatedUser, req.browserId); - void this.internalHooks.onUserUpdate({ user: updatedUser, fields_changed: ['password'] }); + this.internalHooks.onUserUpdate({ user: updatedUser, fields_changed: ['password'] }); this.eventService.emit('user-updated', { user: updatedUser, fieldsChanged: ['password'] }); await this.externalHooks.run('user.password.update', [updatedUser.email, updatedUser.password]); @@ -186,7 +186,7 @@ export class MeController { this.logger.info('User survey updated successfully', { userId: req.user.id }); - void this.internalHooks.onPersonalizationSurveySubmitted(req.user.id, personalizationAnswers); + this.internalHooks.onPersonalizationSurveySubmitted(req.user.id, personalizationAnswers); return { success: true }; } diff --git a/packages/cli/src/controllers/owner.controller.ts b/packages/cli/src/controllers/owner.controller.ts index 6077026c90202..9b86ac41ab313 100644 --- a/packages/cli/src/controllers/owner.controller.ts +++ b/packages/cli/src/controllers/owner.controller.ts @@ -85,7 +85,7 @@ export class OwnerController { this.authService.issueCookie(res, owner, req.browserId); - void this.internalHooks.onInstanceOwnerSetup({ user_id: owner.id }); + this.internalHooks.onInstanceOwnerSetup({ user_id: owner.id }); return await this.userService.toPublic(owner, { posthog: this.postHog, withScopes: true }); } diff --git a/packages/cli/src/controllers/passwordReset.controller.ts b/packages/cli/src/controllers/passwordReset.controller.ts index c548e607fd9d4..e17a0f15efe63 100644 --- a/packages/cli/src/controllers/passwordReset.controller.ts +++ b/packages/cli/src/controllers/passwordReset.controller.ts @@ -120,7 +120,7 @@ export class PasswordResetController { domain: this.urlService.getInstanceBaseUrl(), }); } catch (error) { - void this.internalHooks.onEmailFailed({ + this.internalHooks.onEmailFailed({ user, message_type: 'Reset password', public_api: false, @@ -132,13 +132,13 @@ export class PasswordResetController { } this.logger.info('Sent password reset email successfully', { userId: user.id, email }); - void this.internalHooks.onUserTransactionalEmail({ + this.internalHooks.onUserTransactionalEmail({ user_id: id, message_type: 'Reset password', public_api: false, }); - void this.internalHooks.onUserPasswordResetRequestClick({ user }); + this.internalHooks.onUserPasswordResetRequestClick({ user }); this.eventService.emit('user-password-reset-request-click', { user }); } @@ -171,7 +171,7 @@ export class PasswordResetController { } this.logger.info('Reset-password token resolved successfully', { userId: user.id }); - void this.internalHooks.onUserPasswordResetEmailClick({ user }); + this.internalHooks.onUserPasswordResetEmailClick({ user }); this.eventService.emit('user-password-reset-email-click', { user }); } @@ -215,13 +215,13 @@ export class PasswordResetController { this.authService.issueCookie(res, user, req.browserId); - void this.internalHooks.onUserUpdate({ user, fields_changed: ['password'] }); + this.internalHooks.onUserUpdate({ user, fields_changed: ['password'] }); this.eventService.emit('user-updated', { user, fieldsChanged: ['password'] }); // if this user used to be an LDAP users const ldapIdentity = user?.authIdentities?.find((i) => i.providerType === 'ldap'); if (ldapIdentity) { - void this.internalHooks.onUserSignup(user, { + this.internalHooks.onUserSignup(user, { user_type: 'email', was_disabled_ldap_user: true, }); diff --git a/packages/cli/src/controllers/users.controller.ts b/packages/cli/src/controllers/users.controller.ts index 0fd8481ed8813..f0815aa54d405 100644 --- a/packages/cli/src/controllers/users.controller.ts +++ b/packages/cli/src/controllers/users.controller.ts @@ -253,7 +253,7 @@ export class UsersController { await trx.delete(User, { id: userToDelete.id }); }); - void this.internalHooks.onUserDeletion({ + this.internalHooks.onUserDeletion({ user: req.user, telemetryData, publicApi: false, @@ -294,7 +294,7 @@ export class UsersController { await this.userService.update(targetUser.id, { role: payload.newRoleName }); - void this.internalHooks.onUserRoleChange({ + this.internalHooks.onUserRoleChange({ user: req.user, target_user_id: targetUser.id, target_user_new_role: ['global', payload.newRoleName].join(' '), diff --git a/packages/cli/src/services/frontend.service.ts b/packages/cli/src/services/frontend.service.ts index 918de1793518c..4ada98a9cf7d7 100644 --- a/packages/cli/src/services/frontend.service.ts +++ b/packages/cli/src/services/frontend.service.ts @@ -244,7 +244,7 @@ export class FrontendService { } getSettings(pushRef?: string): IN8nUISettings { - void this.internalHooks.onFrontendSettingsAPI(pushRef); + this.internalHooks.onFrontendSettingsAPI(pushRef); const restEndpoint = config.getEnv('endpoints.rest'); diff --git a/packages/cli/src/services/user.service.ts b/packages/cli/src/services/user.service.ts index cc50e09f74607..007054c6aa980 100644 --- a/packages/cli/src/services/user.service.ts +++ b/packages/cli/src/services/user.service.ts @@ -144,14 +144,14 @@ export class UserService { if (result.emailSent) { invitedUser.user.emailSent = true; delete invitedUser.user?.inviteAcceptUrl; - void Container.get(InternalHooks).onUserTransactionalEmail({ + Container.get(InternalHooks).onUserTransactionalEmail({ user_id: id, message_type: 'New user invite', public_api: false, }); } - void Container.get(InternalHooks).onUserInvite({ + Container.get(InternalHooks).onUserInvite({ user: owner, target_user_id: Object.values(toInviteUsers), public_api: false, @@ -164,7 +164,7 @@ export class UserService { }); } catch (e) { if (e instanceof Error) { - void Container.get(InternalHooks).onEmailFailed({ + Container.get(InternalHooks).onEmailFailed({ user: owner, message_type: 'New user invite', public_api: false, diff --git a/packages/cli/src/telemetry/index.ts b/packages/cli/src/telemetry/index.ts index 82949f3a37099..2d762297304cc 100644 --- a/packages/cli/src/telemetry/index.ts +++ b/packages/cli/src/telemetry/index.ts @@ -88,30 +88,28 @@ export class Telemetry { ); // every 6 hours } - private async pulse(): Promise { + private async pulse() { if (!this.rudderStack) { return; } - const allPromises = Object.keys(this.executionCountsBuffer) - .filter((workflowId) => { - const data = this.executionCountsBuffer[workflowId]; - const sum = - (data.manual_error?.count ?? 0) + - (data.manual_success?.count ?? 0) + - (data.prod_error?.count ?? 0) + - (data.prod_success?.count ?? 0); - return sum > 0; - }) - .map(async (workflowId) => { - const promise = this.track('Workflow execution count', { - event_version: '2', - workflow_id: workflowId, - ...this.executionCountsBuffer[workflowId], - }); - - return await promise; + const workflowIdsToReport = Object.keys(this.executionCountsBuffer).filter((workflowId) => { + const data = this.executionCountsBuffer[workflowId]; + const sum = + (data.manual_error?.count ?? 0) + + (data.manual_success?.count ?? 0) + + (data.prod_error?.count ?? 0) + + (data.prod_success?.count ?? 0); + return sum > 0; + }); + + for (const workflowId of workflowIdsToReport) { + this.track('Workflow execution count', { + event_version: '2', + workflow_id: workflowId, + ...this.executionCountsBuffer[workflowId], }); + } this.executionCountsBuffer = {}; @@ -131,11 +129,11 @@ export class Telemetry { team_projects: (await Container.get(ProjectRepository).getProjectCounts()).team, project_role_count: await Container.get(ProjectRelationRepository).countUsersByRole(), }; - allPromises.push(this.track('pulse', pulsePacket)); - return await Promise.all(allPromises); + + this.track('pulse', pulsePacket); } - async trackWorkflowExecution(properties: IExecutionTrackProperties): Promise { + trackWorkflowExecution(properties: IExecutionTrackProperties) { if (this.rudderStack) { const execTime = new Date(); const workflowId = properties.workflow_id; @@ -164,7 +162,7 @@ export class Telemetry { properties.is_manual && properties.error_node_type?.startsWith('n8n-nodes-base') ) { - void this.track('Workflow execution errored', properties); + this.track('Workflow execution errored', properties); } } } @@ -172,63 +170,52 @@ export class Telemetry { async trackN8nStop(): Promise { clearInterval(this.pulseIntervalReference); - // Send the stopped event and make sure it gets flushed right away - await Promise.all([ - this.track('User instance stopped'), - this.postHog.stop(), - this.rudderStack?.flush(), - ]); + this.track('User instance stopped'); + + await Promise.all([this.postHog.stop(), this.rudderStack?.flush()]); } - async identify(traits?: { - [key: string]: string | number | boolean | object | undefined | null; - }): Promise { + identify(traits?: { [key: string]: string | number | boolean | object | undefined | null }) { + if (!this.rudderStack) { + return; + } + const { instanceId } = this.instanceSettings; - return await new Promise((resolve) => { - if (this.rudderStack) { - this.rudderStack.identify( - { - userId: instanceId, - traits: { ...traits, instanceId }, - }, - resolve, - ); - } else { - resolve(); - } + + this.rudderStack.identify({ + userId: instanceId, + traits: { ...traits, instanceId }, }); } - async track( + track( eventName: string, properties: ITelemetryTrackProperties = {}, { withPostHog } = { withPostHog: false }, // whether to additionally track with PostHog - ): Promise { - const { instanceId } = this.instanceSettings; - return await new Promise((resolve) => { - if (!this.rudderStack) { - return resolve(); - } + ) { + if (!this.rudderStack) { + return; + } - const { user_id } = properties; - const updatedProperties = { - ...properties, - instance_id: instanceId, - version_cli: N8N_VERSION, - }; + const { instanceId } = this.instanceSettings; + const { user_id } = properties; + const updatedProperties = { + ...properties, + instance_id: instanceId, + version_cli: N8N_VERSION, + }; - const payload = { - userId: `${instanceId}${user_id ? `#${user_id}` : ''}`, - event: eventName, - properties: updatedProperties, - }; + const payload = { + userId: `${instanceId}${user_id ? `#${user_id}` : ''}`, + event: eventName, + properties: updatedProperties, + }; - if (withPostHog) { - this.postHog?.track(payload); - } + if (withPostHog) { + this.postHog?.track(payload); + } - return this.rudderStack.track(payload, resolve); - }); + return this.rudderStack.track(payload); } // test helpers diff --git a/packages/cli/src/telemetry/telemetry-event-relay.service.ts b/packages/cli/src/telemetry/telemetry-event-relay.service.ts index 63c7fe4d11e74..bb1d4b8d560a2 100644 --- a/packages/cli/src/telemetry/telemetry-event-relay.service.ts +++ b/packages/cli/src/telemetry/telemetry-event-relay.service.ts @@ -104,7 +104,7 @@ export class TelemetryEventRelay { } private teamProjectUpdated({ userId, role, members, projectId }: Event['team-project-updated']) { - void this.telemetry.track('Project settings updated', { + this.telemetry.track('Project settings updated', { user_id: userId, role, // eslint-disable-next-line @typescript-eslint/no-shadow @@ -120,7 +120,7 @@ export class TelemetryEventRelay { removalType, targetProjectId, }: Event['team-project-deleted']) { - void this.telemetry.track('User deleted project', { + this.telemetry.track('User deleted project', { user_id: userId, role, project_id: projectId, @@ -130,7 +130,7 @@ export class TelemetryEventRelay { } private teamProjectCreated({ userId, role }: Event['team-project-created']) { - void this.telemetry.track('User created project', { + this.telemetry.track('User created project', { user_id: userId, role, }); @@ -142,7 +142,7 @@ export class TelemetryEventRelay { repoType, connected, }: Event['source-control-settings-updated']) { - void this.telemetry.track('User updated source control settings', { + this.telemetry.track('User updated source control settings', { branch_name: branchName, read_only_instance: readOnlyInstance, repo_type: repoType, @@ -155,7 +155,7 @@ export class TelemetryEventRelay { workflowConflicts, credConflicts, }: Event['source-control-user-started-pull-ui']) { - void this.telemetry.track('User started pull via UI', { + this.telemetry.track('User started pull via UI', { workflow_updates: workflowUpdates, workflow_conflicts: workflowConflicts, cred_conflicts: credConflicts, @@ -165,7 +165,7 @@ export class TelemetryEventRelay { private sourceControlUserFinishedPullUi({ workflowUpdates, }: Event['source-control-user-finished-pull-ui']) { - void this.telemetry.track('User finished pull via UI', { + this.telemetry.track('User finished pull via UI', { workflow_updates: workflowUpdates, }); } @@ -178,7 +178,7 @@ export class TelemetryEventRelay { workflow_updates: workflowUpdates, forced, }); - void this.telemetry.track('User pulled via API', { + this.telemetry.track('User pulled via API', { workflow_updates: workflowUpdates, forced, }); @@ -191,7 +191,7 @@ export class TelemetryEventRelay { credsEligibleWithConflicts, variablesEligible, }: Event['source-control-user-started-push-ui']) { - void this.telemetry.track('User started push via UI', { + this.telemetry.track('User started push via UI', { workflows_eligible: workflowsEligible, workflows_eligible_with_conflicts: workflowsEligibleWithConflicts, creds_eligible: credsEligible, @@ -206,7 +206,7 @@ export class TelemetryEventRelay { credsPushed, variablesPushed, }: Event['source-control-user-finished-push-ui']) { - void this.telemetry.track('User finished push via UI', { + this.telemetry.track('User finished push via UI', { workflows_eligible: workflowsEligible, workflows_pushed: workflowsPushed, creds_pushed: credsPushed, @@ -215,13 +215,13 @@ export class TelemetryEventRelay { } private licenseRenewalAttempted({ success }: Event['license-renewal-attempted']) { - void this.telemetry.track('Instance attempted to refresh license', { + this.telemetry.track('Instance attempted to refresh license', { success, }); } private variableCreated() { - void this.telemetry.track('User created variable'); + this.telemetry.track('User created variable'); } private externalSecretsProviderSettingsSaved({ @@ -231,7 +231,7 @@ export class TelemetryEventRelay { isNew, errorMessage, }: Event['external-secrets-provider-settings-saved']) { - void this.telemetry.track('User updated external secrets settings', { + this.telemetry.track('User updated external secrets settings', { user_id: userId, vault_type: vaultType, is_valid: isValid, @@ -241,7 +241,7 @@ export class TelemetryEventRelay { } private publicApiInvoked({ userId, path, method, apiVersion }: Event['public-api-invoked']) { - void this.telemetry.track('User invoked API', { + this.telemetry.track('User invoked API', { user_id: userId, path, method, @@ -252,7 +252,7 @@ export class TelemetryEventRelay { private publicApiKeyCreated(event: Event['public-api-key-created']) { const { user, publicApi } = event; - void this.telemetry.track('API key created', { + this.telemetry.track('API key created', { user_id: user.id, public_api: publicApi, }); @@ -261,7 +261,7 @@ export class TelemetryEventRelay { private publicApiKeyDeleted(event: Event['public-api-key-deleted']) { const { user, publicApi } = event; - void this.telemetry.track('API key deleted', { + this.telemetry.track('API key deleted', { user_id: user.id, public_api: publicApi, }); @@ -278,7 +278,7 @@ export class TelemetryEventRelay { packageAuthorEmail, failureReason, }: Event['community-package-installed']) { - void this.telemetry.track('cnr package install finished', { + this.telemetry.track('cnr package install finished', { user_id: user.id, input_string: inputString, package_name: packageName, @@ -300,7 +300,7 @@ export class TelemetryEventRelay { packageAuthor, packageAuthorEmail, }: Event['community-package-updated']) { - void this.telemetry.track('cnr package updated', { + this.telemetry.track('cnr package updated', { user_id: user.id, package_name: packageName, package_version_current: packageVersionCurrent, @@ -319,7 +319,7 @@ export class TelemetryEventRelay { packageAuthor, packageAuthorEmail, }: Event['community-package-deleted']) { - void this.telemetry.track('cnr package deleted', { + this.telemetry.track('cnr package deleted', { user_id: user.id, package_name: packageName, package_version: packageVersion, @@ -336,7 +336,7 @@ export class TelemetryEventRelay { projectId, projectType, }: Event['credentials-created']) { - void this.telemetry.track('User created credentials', { + this.telemetry.track('User created credentials', { user_id: user.id, credential_type: credentialType, credential_id: credentialId, @@ -353,7 +353,7 @@ export class TelemetryEventRelay { userIdsShareesAdded, shareesRemoved, }: Event['credentials-shared']) { - void this.telemetry.track('User updated cred sharing', { + this.telemetry.track('User updated cred sharing', { user_id: user.id, credential_type: credentialType, credential_id: credentialId, @@ -364,7 +364,7 @@ export class TelemetryEventRelay { } private credentialsUpdated({ user, credentialId, credentialType }: Event['credentials-updated']) { - void this.telemetry.track('User updated credentials', { + this.telemetry.track('User updated credentials', { user_id: user.id, credential_type: credentialType, credential_id: credentialId, @@ -372,7 +372,7 @@ export class TelemetryEventRelay { } private credentialsDeleted({ user, credentialId, credentialType }: Event['credentials-deleted']) { - void this.telemetry.track('User deleted credentials', { + this.telemetry.track('User deleted credentials', { user_id: user.id, credential_type: credentialType, credential_id: credentialId, @@ -385,7 +385,7 @@ export class TelemetryEventRelay { usersSynced, error, }: Event['ldap-general-sync-finished']) { - void this.telemetry.track('Ldap general sync finished', { + this.telemetry.track('Ldap general sync finished', { type, succeeded, users_synced: usersSynced, @@ -407,7 +407,7 @@ export class TelemetryEventRelay { loginLabel, loginEnabled, }: Event['ldap-settings-updated']) { - void this.telemetry.track('User updated Ldap settings', { + this.telemetry.track('User updated Ldap settings', { user_id: userId, loginIdAttribute, firstNameAttribute, @@ -424,11 +424,11 @@ export class TelemetryEventRelay { } private ldapLoginSyncFailed({ error }: Event['ldap-login-sync-failed']) { - void this.telemetry.track('Ldap login sync failed', { error }); + this.telemetry.track('Ldap login sync failed', { error }); } private loginFailedDueToLdapDisabled({ userId }: Event['login-failed-due-to-ldap-disabled']) { - void this.telemetry.track('User login failed since ldap disabled', { user_ud: userId }); + this.telemetry.track('User login failed since ldap disabled', { user_ud: userId }); } private async serverStarted() { @@ -489,12 +489,10 @@ export class TelemetryEventRelay { where: {}, }); - void Promise.all([ - this.telemetry.identify(info), - this.telemetry.track('Instance started', { - ...info, - earliest_workflow_created: firstWorkflow?.createdAt, - }), - ]); + this.telemetry.identify(info); + this.telemetry.track('Instance started', { + ...info, + earliest_workflow_created: firstWorkflow?.createdAt, + }); } } diff --git a/packages/cli/src/workflows/workflow.service.ts b/packages/cli/src/workflows/workflow.service.ts index cf742acecef2e..7c0cbfc242e1b 100644 --- a/packages/cli/src/workflows/workflow.service.ts +++ b/packages/cli/src/workflows/workflow.service.ts @@ -282,7 +282,7 @@ export class WorkflowService { await this.workflowRepository.delete(workflowId); await this.binaryDataService.deleteMany(idsForDeletion); - void Container.get(InternalHooks).onWorkflowDeleted(user, workflowId, false); + Container.get(InternalHooks).onWorkflowDeleted(user, workflowId, false); this.eventService.emit('workflow-deleted', { user, workflowId }); await this.externalHooks.run('workflow.afterDelete', [workflowId]); diff --git a/packages/cli/src/workflows/workflows.controller.ts b/packages/cli/src/workflows/workflows.controller.ts index d0ec019586279..05863edb84c04 100644 --- a/packages/cli/src/workflows/workflows.controller.ts +++ b/packages/cli/src/workflows/workflows.controller.ts @@ -179,7 +179,7 @@ export class WorkflowsController { delete savedWorkflowWithMetaData.shared; await this.externalHooks.run('workflow.afterCreate', [savedWorkflow]); - void this.internalHooks.onWorkflowCreated(req.user, newWorkflow, project!, false); + this.internalHooks.onWorkflowCreated(req.user, newWorkflow, project!, false); this.eventService.emit('workflow-created', { user: req.user, workflow: newWorkflow }); const scopes = await this.workflowService.getWorkflowScopes(req.user, savedWorkflow.id); @@ -454,7 +454,7 @@ export class WorkflowsController { newShareeIds = toShare; }); - void this.internalHooks.onWorkflowSharingUpdate(workflowId, req.user.id, shareWithIds); + this.internalHooks.onWorkflowSharingUpdate(workflowId, req.user.id, shareWithIds); const projectsRelations = await this.projectRelationRepository.findBy({ projectId: In(newShareeIds), diff --git a/packages/cli/test/unit/Telemetry.test.ts b/packages/cli/test/unit/Telemetry.test.ts index 25f4a808675b0..e055abb1fdb74 100644 --- a/packages/cli/test/unit/Telemetry.test.ts +++ b/packages/cli/test/unit/Telemetry.test.ts @@ -14,11 +14,12 @@ describe('Telemetry', () => { let startPulseSpy: jest.SpyInstance; const spyTrack = jest.spyOn(Telemetry.prototype, 'track').mockName('track'); - const mockRudderStack: Pick = { - flush: (resolve) => resolve?.(), - identify: (data, resolve) => resolve?.(), - track: (data, resolve) => resolve?.(), - }; + const mockRudderStack = mock(); + mockRudderStack.track.mockImplementation(function (_, cb) { + cb?.(); + + return this; + }); let telemetry: Telemetry; const instanceId = 'Telemetry unit test'; @@ -26,9 +27,9 @@ describe('Telemetry', () => { const instanceSettings = mockInstance(InstanceSettings, { instanceId }); beforeAll(() => { - startPulseSpy = jest - .spyOn(Telemetry.prototype as any, 'startPulse') - .mockImplementation(() => {}); + // @ts-expect-error Spying on private method + startPulseSpy = jest.spyOn(Telemetry.prototype, 'startPulse').mockImplementation(() => {}); + jest.useFakeTimers(); jest.setSystemTime(testDateTime); config.set('diagnostics.enabled', true); @@ -49,7 +50,8 @@ describe('Telemetry', () => { await postHog.init(); telemetry = new Telemetry(mock(), postHog, mock(), instanceSettings, mock()); - (telemetry as any).rudderStack = mockRudderStack; + // @ts-expect-error Assigning to private property + telemetry.rudderStack = mockRudderStack; }); afterEach(async () => { @@ -79,30 +81,30 @@ describe('Telemetry', () => { payload.is_manual = true; payload.success = true; const execTime1 = fakeJestSystemTime('2022-01-01 12:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); fakeJestSystemTime('2022-01-01 12:30:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.is_manual = false; payload.success = true; const execTime2 = fakeJestSystemTime('2022-01-01 13:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); fakeJestSystemTime('2022-01-01 12:30:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.is_manual = true; payload.success = false; const execTime3 = fakeJestSystemTime('2022-01-01 14:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); fakeJestSystemTime('2022-01-01 12:30:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.is_manual = false; payload.success = false; const execTime4 = fakeJestSystemTime('2022-01-01 15:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); fakeJestSystemTime('2022-01-01 12:30:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(0); @@ -127,9 +129,9 @@ describe('Telemetry', () => { }; const execTime1 = fakeJestSystemTime('2022-01-01 12:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); fakeJestSystemTime('2022-01-01 12:30:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); let execBuffer = telemetry.getCountsBuffer(); @@ -140,9 +142,9 @@ describe('Telemetry', () => { payload.error_node_type = 'n8n-nodes-base.node-type'; fakeJestSystemTime('2022-01-01 13:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); fakeJestSystemTime('2022-01-01 12:30:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); execBuffer = telemetry.getCountsBuffer(); @@ -163,7 +165,7 @@ describe('Telemetry', () => { // successful execution const execTime1 = fakeJestSystemTime('2022-01-01 12:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(0); @@ -179,7 +181,7 @@ describe('Telemetry', () => { payload.error_node_type = 'n8n-nodes-base.merge'; payload.workflow_id = '2'; - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(0); @@ -198,12 +200,12 @@ describe('Telemetry', () => { payload.error_node_type = 'n8n-nodes-base.merge'; payload.workflow_id = '2'; - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.error_node_type = 'n8n-nodes-base.merge'; payload.workflow_id = '1'; - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(0); execBuffer = telemetry.getCountsBuffer(); @@ -225,7 +227,7 @@ describe('Telemetry', () => { const execTime2 = fakeJestSystemTime('2022-01-01 12:00:00'); payload.error_node_type = 'custom-package.custom-node'; payload.success = false; - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(0); @@ -249,7 +251,7 @@ describe('Telemetry', () => { payload.success = false; payload.error_node_type = 'n8n-nodes-base.merge'; payload.is_manual = true; - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(1); @@ -327,27 +329,27 @@ describe('Telemetry', () => { error_node_type: 'custom-nodes-base.node-type', }; - await telemetry.trackWorkflowExecution(payload); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.is_manual = false; payload.success = true; - await telemetry.trackWorkflowExecution(payload); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.is_manual = true; payload.success = false; - await telemetry.trackWorkflowExecution(payload); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.is_manual = false; payload.success = false; - await telemetry.trackWorkflowExecution(payload); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.workflow_id = '2'; - await telemetry.trackWorkflowExecution(payload); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(0); expect(pulseSpy).toBeCalledTimes(0);