diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index de0313ad016e8..7d151093fac0d 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -333,6 +333,16 @@ export interface IDiagnosticInfo { }; deploymentType: string; binaryDataMode: string; + n8n_multi_user_allowed: boolean; + smtp_set_up: boolean; +} + +export interface ITelemetryUserDeletionData { + user_id: string; + target_user_old_status: 'active' | 'invited'; + migration_strategy?: 'transfer_data' | 'delete_data'; + target_user_id?: string; + migration_user_id?: string; } export interface IInternalHooksClass { @@ -341,15 +351,29 @@ export interface IInternalHooksClass { diagnosticInfo: IDiagnosticInfo, firstWorkflowCreatedAt?: Date, ): Promise; - onPersonalizationSurveySubmitted(answers: IPersonalizationSurveyAnswers): Promise; - onWorkflowCreated(workflow: IWorkflowBase): Promise; - onWorkflowDeleted(workflowId: string): Promise; - onWorkflowSaved(workflow: IWorkflowBase): Promise; + onPersonalizationSurveySubmitted(userId: string, answers: Record): Promise; + onWorkflowCreated(userId: string, workflow: IWorkflowBase): Promise; + onWorkflowDeleted(userId: string, workflowId: string): Promise; + onWorkflowSaved(userId: string, workflow: IWorkflowBase): Promise; onWorkflowPostExecute( executionId: string, workflow: IWorkflowBase, runData?: IRun, + userId?: string, ): Promise; + onUserDeletion(userId: string, userDeletionData: ITelemetryUserDeletionData): Promise; + onUserInvite(userInviteData: { user_id: string; target_user_id: string[] }): Promise; + onUserReinvite(userReinviteData: { user_id: string; target_user_id: string }): Promise; + onUserUpdate(userUpdateData: { user_id: string; fields_changed: string[] }): Promise; + onUserInviteEmailClick(userInviteClickData: { user_id: string }): Promise; + onUserPasswordResetEmailClick(userPasswordResetData: { user_id: string }): Promise; + onUserTransactionalEmail(userTransactionalEmailData: { + user_id: string; + message_type: 'Reset password' | 'New user invite' | 'Resend invite'; + }): Promise; + onUserPasswordResetRequestClick(userPasswordResetData: { user_id: string }): Promise; + onInstanceOwnerSetup(instanceOwnerSetupData: { user_id: string }): Promise; + onUserSignup(userSignupData: { user_id: string }): Promise; } export interface IN8nConfig { diff --git a/packages/cli/src/InternalHooks.ts b/packages/cli/src/InternalHooks.ts index 068885ef13373..f25dd878b8871 100644 --- a/packages/cli/src/InternalHooks.ts +++ b/packages/cli/src/InternalHooks.ts @@ -4,7 +4,7 @@ import { IDataObject, INodeTypes, IRun, TelemetryHelpers } from 'n8n-workflow'; import { IDiagnosticInfo, IInternalHooksClass, - IPersonalizationSurveyAnswers, + ITelemetryUserDeletionData, IWorkflowBase, IWorkflowDb, } from '.'; @@ -34,6 +34,8 @@ export class InternalHooksClass implements IInternalHooksClass { execution_variables: diagnosticInfo.executionVariables, n8n_deployment_type: diagnosticInfo.deploymentType, n8n_binary_data_mode: diagnosticInfo.binaryDataMode, + n8n_multi_user_allowed: diagnosticInfo.n8n_multi_user_allowed, + smtp_set_up: diagnosticInfo.smtp_set_up, }; return Promise.all([ @@ -45,8 +47,12 @@ export class InternalHooksClass implements IInternalHooksClass { ]); } - async onPersonalizationSurveySubmitted(answers: IPersonalizationSurveyAnswers): Promise { + async onPersonalizationSurveySubmitted( + userId: string, + answers: Record, + ): Promise { return this.telemetry.track('User responded to personalization questions', { + user_id: userId, company_size: answers.companySize, coding_skill: answers.codingSkill, work_area: answers.workArea, @@ -56,25 +62,28 @@ export class InternalHooksClass implements IInternalHooksClass { }); } - async onWorkflowCreated(workflow: IWorkflowBase): Promise { + async onWorkflowCreated(userId: string, workflow: IWorkflowBase): Promise { const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes); return this.telemetry.track('User created workflow', { + user_id: userId, workflow_id: workflow.id, node_graph: nodeGraph, node_graph_string: JSON.stringify(nodeGraph), }); } - async onWorkflowDeleted(workflowId: string): Promise { + async onWorkflowDeleted(userId: string, workflowId: string): Promise { return this.telemetry.track('User deleted workflow', { + user_id: userId, workflow_id: workflowId, }); } - async onWorkflowSaved(workflow: IWorkflowDb): Promise { + async onWorkflowSaved(userId: string, workflow: IWorkflowDb): Promise { const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes); return this.telemetry.track('User saved workflow', { + user_id: userId, workflow_id: workflow.id, node_graph: nodeGraph, node_graph_string: JSON.stringify(nodeGraph), @@ -87,6 +96,7 @@ export class InternalHooksClass implements IInternalHooksClass { executionId: string, workflow: IWorkflowBase, runData?: IRun, + userId?: string, ): Promise { const promises = [Promise.resolve()]; const properties: IDataObject = { @@ -95,6 +105,10 @@ export class InternalHooksClass implements IInternalHooksClass { version_cli: this.versionCli, }; + if (userId) { + properties.user_id = userId; + } + if (runData !== undefined) { properties.execution_mode = runData.mode; properties.success = !!runData.finished; @@ -188,4 +202,62 @@ export class InternalHooksClass implements IInternalHooksClass { return Promise.race([timeoutPromise, this.telemetry.trackN8nStop()]); } + + async onUserDeletion( + userId: string, + userDeletionData: ITelemetryUserDeletionData, + ): Promise { + return this.telemetry.track('User deleted user', { ...userDeletionData, user_id: userId }); + } + + async onUserInvite(userInviteData: { user_id: string; target_user_id: string[] }): Promise { + return this.telemetry.track('User invited new user', userInviteData); + } + + async onUserReinvite(userReinviteData: { + user_id: string; + target_user_id: string; + }): Promise { + return this.telemetry.track('User resent new user invite email', userReinviteData); + } + + async onUserUpdate(userUpdateData: { user_id: string; fields_changed: string[] }): Promise { + return this.telemetry.track('User changed personal settings', userUpdateData); + } + + async onUserInviteEmailClick(userInviteClickData: { user_id: string }): Promise { + return this.telemetry.track('User clicked invite link from email', userInviteClickData); + } + + async onUserPasswordResetEmailClick(userPasswordResetData: { user_id: string }): Promise { + return this.telemetry.track( + 'User clicked password reset link from email', + userPasswordResetData, + ); + } + + async onUserTransactionalEmail(userTransactionalEmailData: { + user_id: string; + message_type: 'Reset password' | 'New user invite' | 'Resend invite'; + }): Promise { + return this.telemetry.track( + 'Instance sent transactional email to user', + userTransactionalEmailData, + ); + } + + async onUserPasswordResetRequestClick(userPasswordResetData: { user_id: string }): Promise { + return this.telemetry.track( + 'User requested password reset while logged out', + userPasswordResetData, + ); + } + + async onInstanceOwnerSetup(instanceOwnerSetupData: { user_id: string }): Promise { + return this.telemetry.track('Owner finished instance setup', instanceOwnerSetupData); + } + + async onUserSignup(userSignupData: { user_id: string }): Promise { + return this.telemetry.track('User signed up', userSignupData); + } } diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index fbf5777a36a00..eb1a261a0c52e 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -787,7 +787,7 @@ class App { } await this.externalHooks.run('workflow.afterCreate', [savedWorkflow]); - void InternalHooksManager.getInstance().onWorkflowCreated(newWorkflow); + void InternalHooksManager.getInstance().onWorkflowCreated(req.user.id, newWorkflow); const { id, ...rest } = savedWorkflow; @@ -1128,7 +1128,7 @@ class App { await Db.collections.Workflow!.delete(workflowId); - void InternalHooksManager.getInstance().onWorkflowDeleted(workflowId); + void InternalHooksManager.getInstance().onWorkflowDeleted(req.user.id, workflowId); await this.externalHooks.run('workflow.afterDelete', [workflowId]); return true; @@ -2925,6 +2925,10 @@ export async function start(): Promise { }, deploymentType: config.get('deployment.type'), binaryDataMode: binarDataConfig.mode, + n8n_multi_user_allowed: + config.get('userManagement.disabled') === false || + config.get('userManagement.hasOwner') === true, + smtp_set_up: config.get('userManagement.emails.mode') === 'smtp', }; void Db.collections diff --git a/packages/cli/src/UserManagement/routes/auth.ts b/packages/cli/src/UserManagement/routes/auth.ts index b98b40ecaf707..f53c716e47d74 100644 --- a/packages/cli/src/UserManagement/routes/auth.ts +++ b/packages/cli/src/UserManagement/routes/auth.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable import/no-cycle */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ @@ -10,6 +11,7 @@ import { issueCookie, resolveJwt } from '../auth/jwt'; import { N8nApp, PublicUser } from '../Interfaces'; import { isInstanceOwnerSetup, sanitizeUser } from '../UserManagementHelper'; import { User } from '../../databases/entities/User'; +import type { LoginRequest } from '../../requests'; export function authenticationMethods(this: N8nApp): void { /** @@ -19,7 +21,7 @@ export function authenticationMethods(this: N8nApp): void { */ this.app.post( `/${this.restEndpoint}/login`, - ResponseHelper.send(async (req: Request, res: Response): Promise => { + ResponseHelper.send(async (req: LoginRequest, res: Response): Promise => { if (!req.body.email) { throw new Error('Email is required to log in'); } @@ -32,7 +34,7 @@ export function authenticationMethods(this: N8nApp): void { try { user = await Db.collections.User!.findOne( { - email: req.body.email as string, + email: req.body.email, }, { relations: ['globalRole'], @@ -107,7 +109,7 @@ export function authenticationMethods(this: N8nApp): void { */ this.app.post( `/${this.restEndpoint}/logout`, - ResponseHelper.send(async (req: Request, res: Response): Promise => { + ResponseHelper.send(async (_, res: Response): Promise => { res.clearCookie(AUTH_COOKIE_NAME); return { loggedOut: true, diff --git a/packages/cli/src/UserManagement/routes/me.ts b/packages/cli/src/UserManagement/routes/me.ts index e804b09bb6717..1c4ed9f427afe 100644 --- a/packages/cli/src/UserManagement/routes/me.ts +++ b/packages/cli/src/UserManagement/routes/me.ts @@ -6,7 +6,7 @@ import express = require('express'); import validator from 'validator'; import { LoggerProxy as Logger } from 'n8n-workflow'; -import { Db, ResponseHelper } from '../..'; +import { Db, InternalHooksManager, ResponseHelper } from '../..'; import { issueCookie } from '../auth/jwt'; import { N8nApp, PublicUser } from '../Interfaces'; import { validatePassword, sanitizeUser } from '../UserManagementHelper'; @@ -60,6 +60,12 @@ export function meNamespace(this: N8nApp): void { await issueCookie(res, user); + const updatedkeys = Object.keys(req.body); + void InternalHooksManager.getInstance().onUserUpdate({ + user_id: req.user.id, + fields_changed: updatedkeys, + }); + return sanitizeUser(user); }, ), @@ -99,6 +105,11 @@ export function meNamespace(this: N8nApp): void { await issueCookie(res, user); + void InternalHooksManager.getInstance().onUserUpdate({ + user_id: req.user.id, + fields_changed: ['password'], + }); + return { success: true }; }), ); @@ -132,6 +143,11 @@ export function meNamespace(this: N8nApp): void { Logger.info('User survey updated successfully', { userId: req.user.id }); + void InternalHooksManager.getInstance().onPersonalizationSurveySubmitted( + req.user.id, + personalizationAnswers, + ); + return { success: true }; }), ); diff --git a/packages/cli/src/UserManagement/routes/owner.ts b/packages/cli/src/UserManagement/routes/owner.ts index 0ece05e8a870c..a526afc948a9f 100644 --- a/packages/cli/src/UserManagement/routes/owner.ts +++ b/packages/cli/src/UserManagement/routes/owner.ts @@ -5,7 +5,7 @@ import * as express from 'express'; import validator from 'validator'; import { LoggerProxy as Logger } from 'n8n-workflow'; -import { Db, ResponseHelper } from '../..'; +import { Db, InternalHooksManager, ResponseHelper } from '../..'; import config = require('../../../config'); import { validateEntity } from '../../GenericHelpers'; import { AuthenticatedRequest, OwnerRequest } from '../../requests'; @@ -94,6 +94,10 @@ export function ownerNamespace(this: N8nApp): void { await issueCookie(res, owner); + void InternalHooksManager.getInstance().onInstanceOwnerSetup({ + user_id: userId, + }); + return sanitizeUser(owner); }), ); diff --git a/packages/cli/src/UserManagement/routes/passwordReset.ts b/packages/cli/src/UserManagement/routes/passwordReset.ts index 1a5b3aee74c3c..7dbb630283d30 100644 --- a/packages/cli/src/UserManagement/routes/passwordReset.ts +++ b/packages/cli/src/UserManagement/routes/passwordReset.ts @@ -9,7 +9,7 @@ import validator from 'validator'; import { IsNull, MoreThanOrEqual, Not } from 'typeorm'; import { LoggerProxy as Logger } from 'n8n-workflow'; -import { Db, ResponseHelper } from '../..'; +import { Db, InternalHooksManager, ResponseHelper } from '../..'; import { N8nApp } from '../Interfaces'; import { validatePassword } from '../UserManagementHelper'; import * as UserManagementMailer from '../email'; @@ -98,6 +98,14 @@ export function passwordResetNamespace(this: N8nApp): void { } Logger.info('Sent password reset email successfully', { userId: user.id, email }); + void InternalHooksManager.getInstance().onUserTransactionalEmail({ + user_id: id, + message_type: 'Reset password', + }); + + void InternalHooksManager.getInstance().onUserPasswordResetRequestClick({ + user_id: id, + }); }), ); @@ -142,6 +150,9 @@ export function passwordResetNamespace(this: N8nApp): void { } Logger.info('Reset-password token resolved successfully', { userId: id }); + void InternalHooksManager.getInstance().onUserPasswordResetEmailClick({ + user_id: id, + }); }), ); diff --git a/packages/cli/src/UserManagement/routes/users.ts b/packages/cli/src/UserManagement/routes/users.ts index 2b649d01cee05..a91b0cfa52dc8 100644 --- a/packages/cli/src/UserManagement/routes/users.ts +++ b/packages/cli/src/UserManagement/routes/users.ts @@ -7,7 +7,7 @@ import { genSaltSync, hashSync } from 'bcryptjs'; import validator from 'validator'; import { LoggerProxy as Logger } from 'n8n-workflow'; -import { Db, ResponseHelper } from '../..'; +import { Db, InternalHooksManager, ITelemetryUserDeletionData, ResponseHelper } from '../..'; import { N8nApp, PublicUser } from '../Interfaces'; import { UserRequest } from '../../requests'; import { getInstanceBaseUrl, sanitizeUser, validatePassword } from '../UserManagementHelper'; @@ -139,6 +139,11 @@ export function usersNamespace(this: N8nApp): void { }), ); }); + + void InternalHooksManager.getInstance().onUserInvite({ + user_id: req.user.id, + target_user_id: Object.values(createUsers) as string[], + }); } catch (error) { Logger.error('Failed to create user shells', { userShells: createUsers }); throw new ResponseHelper.ResponseError('An error occurred during user creation'); @@ -170,7 +175,12 @@ export function usersNamespace(this: N8nApp): void { email, }, }; - if (!result?.success) { + if (result?.success) { + void InternalHooksManager.getInstance().onUserTransactionalEmail({ + user_id: id!, + message_type: 'New user invite', + }); + } else { Logger.error('Failed to send email', { userId: req.user.id, inviteAcceptUrl, @@ -254,6 +264,10 @@ export function usersNamespace(this: N8nApp): void { throw new ResponseHelper.ResponseError('Invalid request', undefined, 400); } + void InternalHooksManager.getInstance().onUserInviteEmailClick({ + user_id: inviteeId, + }); + const { firstName, lastName } = inviter; return { inviter: { firstName, lastName } }; @@ -320,6 +334,10 @@ export function usersNamespace(this: N8nApp): void { await issueCookie(res, updatedUser); + void InternalHooksManager.getInstance().onUserSignup({ + user_id: invitee.id, + }); + return sanitizeUser(updatedUser); }), ); @@ -420,6 +438,20 @@ export function usersNamespace(this: N8nApp): void { await transactionManager.delete(User, { id: userToDelete.id }); }); + const telemetryData: ITelemetryUserDeletionData = { + user_id: req.user.id, + target_user_old_status: userToDelete.isPending ? 'invited' : 'active', + target_user_id: idToDelete, + }; + + telemetryData.migration_strategy = transferId ? 'transfer_data' : 'delete_data'; + + if (transferId) { + telemetryData.migration_user_id = transferId; + } + + void InternalHooksManager.getInstance().onUserDeletion(req.user.id, telemetryData); + return { success: true }; }), ); @@ -495,6 +527,16 @@ export function usersNamespace(this: N8nApp): void { ); } + void InternalHooksManager.getInstance().onUserReinvite({ + user_id: req.user.id, + target_user_id: reinvitee.id, + }); + + void InternalHooksManager.getInstance().onUserTransactionalEmail({ + user_id: reinvitee.id, + message_type: 'Resend invite', + }); + return { success: true }; }), ); diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index 0b0d4ec004870..ee2b2277444cd 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -967,7 +967,12 @@ export async function executeWorkflow( } await externalHooks.run('workflow.postExecute', [data, workflowData, executionId]); - void InternalHooksManager.getInstance().onWorkflowPostExecute(executionId, workflowData, data); + void InternalHooksManager.getInstance().onWorkflowPostExecute( + executionId, + workflowData, + data, + additionalData.userId, + ); if (data.finished === true) { // Workflow did finish successfully diff --git a/packages/cli/src/WorkflowRunner.ts b/packages/cli/src/WorkflowRunner.ts index c6c86b841de45..6d4abbaaaf957 100644 --- a/packages/cli/src/WorkflowRunner.ts +++ b/packages/cli/src/WorkflowRunner.ts @@ -178,6 +178,7 @@ export class WorkflowRunner { executionId!, data.workflowData, executionData, + data.userId, ); }) .catch((error) => { diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index 44317b851fa6d..8c2d457c4feef 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -311,6 +311,7 @@ export class WorkflowRunnerProcess { executionId, workflowData, result, + additionalData.userId, ); await sendToParentProcess('finishExecution', { executionId, result }); delete this.childExecutions[executionId]; diff --git a/packages/cli/src/requests.d.ts b/packages/cli/src/requests.d.ts index bfa21565784c9..72e1ffe0e29d3 100644 --- a/packages/cli/src/requests.d.ts +++ b/packages/cli/src/requests.d.ts @@ -14,6 +14,13 @@ import { User } from './databases/entities/User'; import type { IExecutionDeleteFilter, IWorkflowDb } from '.'; import type { PublicUser } from './UserManagement/Interfaces'; +export type AuthlessRequest< + RouteParams = {}, + ResponseBody = {}, + RequestBody = {}, + RequestQuery = {}, +> = express.Request; + export type AuthenticatedRequest< RouteParams = {}, ResponseBody = {}, @@ -159,11 +166,11 @@ export declare namespace OwnerRequest { // ---------------------------------- export declare namespace PasswordResetRequest { - export type Email = AuthenticatedRequest<{}, {}, Pick>; + export type Email = AuthlessRequest<{}, {}, Pick>; - export type Credentials = AuthenticatedRequest<{}, {}, {}, { userId?: string; token?: string }>; + export type Credentials = AuthlessRequest<{}, {}, {}, { userId?: string; token?: string }>; - export type NewPassword = AuthenticatedRequest< + export type NewPassword = AuthlessRequest< {}, {}, Pick & { token?: string; userId?: string } @@ -177,7 +184,7 @@ export declare namespace PasswordResetRequest { export declare namespace UserRequest { export type Invite = AuthenticatedRequest<{}, {}, Array<{ email: string }>>; - export type ResolveSignUp = AuthenticatedRequest< + export type ResolveSignUp = AuthlessRequest< {}, {}, {}, @@ -193,7 +200,7 @@ export declare namespace UserRequest { export type Reinvite = AuthenticatedRequest<{ id: string }>; - export type Update = AuthenticatedRequest< + export type Update = AuthlessRequest< { id: string }, {}, { @@ -205,6 +212,19 @@ export declare namespace UserRequest { >; } +// ---------------------------------- +// /login +// ---------------------------------- + +export type LoginRequest = AuthlessRequest< + {}, + {}, + { + email: string; + password: string; + } +>; + // ---------------------------------- // oauth endpoints // ---------------------------------- diff --git a/packages/cli/src/telemetry/index.ts b/packages/cli/src/telemetry/index.ts index 6b678f099fe60..69a4bad45617f 100644 --- a/packages/cli/src/telemetry/index.ts +++ b/packages/cli/src/telemetry/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable import/no-cycle */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import TelemetryClient = require('@rudderstack/rudder-sdk-node'); @@ -184,12 +185,17 @@ export class Telemetry { }); } - async track(eventName: string, properties?: IDataObject): Promise { + async track( + eventName: string, + properties: { [key: string]: unknown; user_id?: string } = {}, + ): Promise { return new Promise((resolve) => { if (this.client) { + const { user_id } = properties; + Object.assign(properties, { instance_id: this.instanceId }); this.client.track( { - userId: this.instanceId, + userId: `${this.instanceId}${user_id ? `#${user_id}` : ''}`, anonymousId: '000000000000', event: eventName, properties, diff --git a/packages/cli/test/integration/auth.endpoints.test.ts b/packages/cli/test/integration/auth.endpoints.test.ts index a29504a1b648e..23ef7e3e3d6f5 100644 --- a/packages/cli/test/integration/auth.endpoints.test.ts +++ b/packages/cli/test/integration/auth.endpoints.test.ts @@ -26,6 +26,7 @@ beforeAll(async () => { globalOwnerRole = await getGlobalOwnerRole(); utils.initTestLogger(); + utils.initTestTelemetry(); }); beforeEach(async () => { diff --git a/packages/cli/test/integration/auth.middleware.test.ts b/packages/cli/test/integration/auth.middleware.test.ts index 3dfb6685ad201..d686496aab2b2 100644 --- a/packages/cli/test/integration/auth.middleware.test.ts +++ b/packages/cli/test/integration/auth.middleware.test.ts @@ -20,6 +20,7 @@ beforeAll(async () => { const initResult = await testDb.init(); testDbName = initResult.testDbName; utils.initTestLogger(); + utils.initTestTelemetry(); }); afterAll(async () => { diff --git a/packages/cli/test/integration/credentials.api.test.ts b/packages/cli/test/integration/credentials.api.test.ts index c1a3737f86ff4..533660ff57a19 100644 --- a/packages/cli/test/integration/credentials.api.test.ts +++ b/packages/cli/test/integration/credentials.api.test.ts @@ -24,6 +24,7 @@ beforeAll(async () => { const credentialOwnerRole = await testDb.getCredentialOwnerRole(); saveCredential = affixRoleToSaveCredential(credentialOwnerRole); + utils.initTestTelemetry(); }); beforeEach(async () => { diff --git a/packages/cli/test/integration/me.endpoints.test.ts b/packages/cli/test/integration/me.endpoints.test.ts index 0da438d5c6895..ca86b85818435 100644 --- a/packages/cli/test/integration/me.endpoints.test.ts +++ b/packages/cli/test/integration/me.endpoints.test.ts @@ -21,6 +21,7 @@ beforeAll(async () => { globalOwnerRole = await testDb.getGlobalOwnerRole(); utils.initTestLogger(); + utils.initTestTelemetry(); }); afterAll(async () => { diff --git a/packages/cli/test/integration/owner.endpoints.test.ts b/packages/cli/test/integration/owner.endpoints.test.ts index f4313301174f9..74e6ba377793c 100644 --- a/packages/cli/test/integration/owner.endpoints.test.ts +++ b/packages/cli/test/integration/owner.endpoints.test.ts @@ -21,6 +21,7 @@ beforeAll(async () => { testDbName = initResult.testDbName; utils.initTestLogger(); + utils.initTestTelemetry(); }); beforeEach(async () => { diff --git a/packages/cli/test/integration/passwordReset.endpoints.test.ts b/packages/cli/test/integration/passwordReset.endpoints.test.ts index 8cb3ef0cc7289..36dd7d8261c58 100644 --- a/packages/cli/test/integration/passwordReset.endpoints.test.ts +++ b/packages/cli/test/integration/passwordReset.endpoints.test.ts @@ -29,6 +29,8 @@ beforeAll(async () => { name: 'owner', scope: 'global', }); + + utils.initTestTelemetry(); utils.initTestLogger(); }); diff --git a/packages/cli/test/integration/shared/utils.ts b/packages/cli/test/integration/shared/utils.ts index 7cbceb19861b8..45f97cfabf6ad 100644 --- a/packages/cli/test/integration/shared/utils.ts +++ b/packages/cli/test/integration/shared/utils.ts @@ -7,14 +7,14 @@ import { URL } from 'url'; import bodyParser = require('body-parser'); import * as util from 'util'; import { createTestAccount } from 'nodemailer'; -import { LoggerProxy } from 'n8n-workflow'; +import { INodeTypes, LoggerProxy } from 'n8n-workflow'; import { UserSettings } from 'n8n-core'; import config = require('../../../config'); import { AUTHLESS_ENDPOINTS, REST_PATH_SEGMENT } from './constants'; import { AUTH_COOKIE_NAME } from '../../../src/constants'; import { addRoutes as authMiddleware } from '../../../src/UserManagement/routes'; -import { Db, ExternalHooks } from '../../../src'; +import { Db, ExternalHooks, InternalHooksManager } from '../../../src'; import { meNamespace as meEndpoints } from '../../../src/UserManagement/routes/me'; import { usersNamespace as usersEndpoints } from '../../../src/UserManagement/routes/users'; import { authenticationMethods as authEndpoints } from '../../../src/UserManagement/routes/auth'; @@ -25,6 +25,7 @@ import { getLogger } from '../../../src/Logger'; import { credentialsController } from '../../../src/api/credentials.api'; import type { User } from '../../../src/databases/entities/User'; +import { Telemetry } from '../../../src/telemetry'; import type { EndpointGroup, SmtpTestAccount } from './types'; import type { N8nApp } from '../../../src/UserManagement/Interfaces'; @@ -88,6 +89,14 @@ export function initTestServer({ return testServer.app; } +export function initTestTelemetry() { + const mockNodeTypes = { nodeTypes: {} } as INodeTypes; + + void InternalHooksManager.init('test-instance-id', 'test-version', mockNodeTypes); + + jest.spyOn(Telemetry.prototype, 'track').mockResolvedValue(); +} + /** * Classify endpoint groups into `routerEndpoints` (newest, using `express.Router`), * and `functionEndpoints` (legacy, namespaced inside a function). diff --git a/packages/cli/test/integration/users.endpoints.test.ts b/packages/cli/test/integration/users.endpoints.test.ts index 6e5b673678379..d1eaee700bdb2 100644 --- a/packages/cli/test/integration/users.endpoints.test.ts +++ b/packages/cli/test/integration/users.endpoints.test.ts @@ -42,6 +42,7 @@ beforeAll(async () => { workflowOwnerRole = fetchedWorkflowOwnerRole; credentialOwnerRole = fetchedCredentialOwnerRole; + utils.initTestTelemetry(); utils.initTestLogger(); });