From c0f3693e8afdadfb5984d71967fcc2597913158f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 5 Aug 2024 12:07:42 +0200 Subject: [PATCH] refactor(core): Decouple user events from internal hooks (no-changelog) (#10292) --- packages/cli/src/Interfaces.ts | 8 - packages/cli/src/InternalHooks.ts | 96 ------------ .../handlers/executions/executions.handler.ts | 14 +- .../v1/handlers/users/users.handler.ee.ts | 22 ++- .../handlers/workflows/workflows.handler.ts | 13 +- packages/cli/src/auth/methods/ldap.ts | 9 +- .../__tests__/me.controller.test.ts | 8 +- .../src/controllers/invitation.controller.ts | 10 +- packages/cli/src/controllers/me.controller.ts | 9 +- .../controllers/passwordReset.controller.ts | 11 +- .../cli/src/controllers/users.controller.ts | 31 ++-- .../log-streaming-event-relay.test.ts | 5 + packages/cli/src/events/relay-event-map.ts | 53 +++++++ .../cli/src/events/telemetry-event-relay.ts | 142 ++++++++++++++++++ packages/cli/src/services/user.service.ts | 10 +- 15 files changed, 260 insertions(+), 181 deletions(-) diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index 25f758185a..8ddbd10fb2 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -267,14 +267,6 @@ export interface IWebhookManager { executeWebhook(req: WebhookRequest, res: Response): Promise; } -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 IVersionNotificationSettings { enabled: boolean; endpoint: string; diff --git a/packages/cli/src/InternalHooks.ts b/packages/cli/src/InternalHooks.ts index af72d6bfec..031a7fe3a6 100644 --- a/packages/cli/src/InternalHooks.ts +++ b/packages/cli/src/InternalHooks.ts @@ -1,9 +1,6 @@ import { Service } from 'typedi'; -import { snakeCase } from 'change-case'; import type { ITelemetryTrackProperties } from 'n8n-workflow'; -import type { AuthProviderType } from '@db/entities/AuthIdentity'; import type { User } from '@db/entities/User'; -import type { ITelemetryUserDeletionData } from '@/Interfaces'; import { WorkflowStatisticsService } from '@/services/workflow-statistics.service'; import { Telemetry } from '@/telemetry'; import { MessageEventBus } from './eventbus/MessageEventBus/MessageEventBus'; @@ -39,16 +36,6 @@ export class InternalHooks { this.telemetry.track('Session started', { session_id: pushRef }); } - 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]; - }); - - this.telemetry.track('User responded to personalization questions', personalizationSurveyData); - } - onWorkflowSharingUpdate(workflowId: string, userId: string, userList: string[]) { const properties: ITelemetryTrackProperties = { workflow_id: workflowId, @@ -67,76 +54,6 @@ export class InternalHooks { return await Promise.race([timeoutPromise, this.telemetry.trackN8nStop()]); } - onUserDeletion(userDeletionData: { - user: User; - telemetryData: ITelemetryUserDeletionData; - publicApi: boolean; - }) { - this.telemetry.track('User deleted user', { - ...userDeletionData.telemetryData, - user_id: userDeletionData.user.id, - public_api: userDeletionData.publicApi, - }); - } - - onUserInvite(userInviteData: { - user: User; - target_user_id: string[]; - public_api: boolean; - email_sent: boolean; - invitee_role: string; - }) { - this.telemetry.track('User invited new user', { - user_id: userInviteData.user.id, - target_user_id: userInviteData.target_user_id, - public_api: userInviteData.public_api, - email_sent: userInviteData.email_sent, - invitee_role: userInviteData.invitee_role, - }); - } - - onUserRoleChange(userRoleChangeData: { - user: User; - target_user_id: string; - public_api: boolean; - target_user_new_role: string; - }) { - const { user, ...rest } = userRoleChangeData; - - this.telemetry.track('User changed role', { user_id: user.id, ...rest }); - } - - onUserRetrievedUser(userRetrievedData: { user_id: string; public_api: boolean }) { - this.telemetry.track('User retrieved user', userRetrievedData); - } - - onUserRetrievedAllUsers(userRetrievedData: { user_id: string; public_api: boolean }) { - this.telemetry.track('User retrieved all users', userRetrievedData); - } - - onUserRetrievedExecution(userRetrievedData: { user_id: string; public_api: boolean }) { - this.telemetry.track('User retrieved execution', userRetrievedData); - } - - onUserRetrievedAllExecutions(userRetrievedData: { user_id: string; public_api: boolean }) { - this.telemetry.track('User retrieved all executions', userRetrievedData); - } - - onUserRetrievedWorkflow(userRetrievedData: { user_id: string; public_api: boolean }) { - this.telemetry.track('User retrieved workflow', userRetrievedData); - } - - onUserRetrievedAllWorkflows(userRetrievedData: { user_id: string; public_api: boolean }) { - this.telemetry.track('User retrieved all workflows', userRetrievedData); - } - - onUserUpdate(userUpdateData: { user: User; fields_changed: string[] }) { - this.telemetry.track('User changed personal settings', { - user_id: userUpdateData.user.id, - fields_changed: userUpdateData.fields_changed, - }); - } - onUserInviteEmailClick(userInviteClickData: { inviter: User; invitee: User }) { this.telemetry.track('User clicked invite link from email', { user_id: userInviteClickData.invitee.id, @@ -172,19 +89,6 @@ export class InternalHooks { this.telemetry.track('Owner finished instance setup', instanceOwnerSetupData); } - onUserSignup( - user: User, - userSignupData: { - user_type: AuthProviderType; - was_disabled_ldap_user: boolean; - }, - ) { - this.telemetry.track('User signed up', { - user_id: user.id, - ...userSignupData, - }); - } - onEmailFailed(failedEmailData: { user: User; message_type: 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 ed078b52d5..ab6927724c 100644 --- a/packages/cli/src/PublicApi/v1/handlers/executions/executions.handler.ts +++ b/packages/cli/src/PublicApi/v1/handlers/executions/executions.handler.ts @@ -7,7 +7,7 @@ import { validCursor } from '../../shared/middlewares/global.middleware'; import type { ExecutionRequest } from '../../../types'; import { getSharedWorkflowIds } from '../workflows/workflows.service'; import { encodeNextCursor } from '../../shared/services/pagination.service'; -import { InternalHooks } from '@/InternalHooks'; +import { EventService } from '@/events/event.service'; import { ExecutionRepository } from '@db/repositories/execution.repository'; import { ConcurrencyControlService } from '@/concurrency/concurrency-control.service'; @@ -78,9 +78,9 @@ export = { return res.status(404).json({ message: 'Not Found' }); } - Container.get(InternalHooks).onUserRetrievedExecution({ - user_id: req.user.id, - public_api: true, + Container.get(EventService).emit('user-retrieved-execution', { + userId: req.user.id, + publicApi: true, }); return res.json(replaceCircularReferences(execution)); @@ -130,9 +130,9 @@ export = { const count = await Container.get(ExecutionRepository).getExecutionsCountForPublicApi(filters); - Container.get(InternalHooks).onUserRetrievedAllExecutions({ - user_id: req.user.id, - public_api: true, + Container.get(EventService).emit('user-retrieved-all-executions', { + userId: req.user.id, + publicApi: true, }); return res.json({ 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 53c568ee69..59a7a4c3e3 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 @@ -11,7 +11,7 @@ import { validLicenseWithUserQuota, } from '../../shared/middlewares/global.middleware'; import type { UserRequest } from '@/requests'; -import { InternalHooks } from '@/InternalHooks'; +import { EventService } from '@/events/event.service'; import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository'; import type { Response } from 'express'; import { InvitationController } from '@/controllers/invitation.controller'; @@ -37,12 +37,10 @@ export = { }); } - const telemetryData = { - user_id: req.user.id, - public_api: true, - }; - - Container.get(InternalHooks).onUserRetrievedUser(telemetryData); + Container.get(EventService).emit('user-retrieved-user', { + userId: req.user.id, + publicApi: true, + }); return res.json(clean(user, { includeRole })); }, @@ -65,12 +63,10 @@ export = { in: _in, }); - const telemetryData = { - user_id: req.user.id, - public_api: true, - }; - - Container.get(InternalHooks).onUserRetrievedAllUsers(telemetryData); + Container.get(EventService).emit('user-retrieved-all-users', { + userId: req.user.id, + publicApi: true, + }); 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 16434ef4e9..d5abac95a1 100644 --- a/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts +++ b/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts @@ -26,7 +26,6 @@ import { updateTags, } from './workflows.service'; import { WorkflowService } from '@/workflows/workflow.service'; -import { InternalHooks } from '@/InternalHooks'; import { WorkflowHistoryService } from '@/workflows/workflowHistory/workflowHistory.service.ee'; import { SharedWorkflowRepository } from '@/databases/repositories/sharedWorkflow.repository'; import { TagRepository } from '@/databases/repositories/tag.repository'; @@ -119,9 +118,9 @@ export = { return res.status(404).json({ message: 'Not Found' }); } - Container.get(InternalHooks).onUserRetrievedWorkflow({ - user_id: req.user.id, - public_api: true, + Container.get(EventService).emit('user-retrieved-workflow', { + userId: req.user.id, + publicApi: true, }); return res.json(workflow); @@ -185,9 +184,9 @@ export = { ...(!config.getEnv('workflowTagsDisabled') && { relations: ['tags'] }), }); - Container.get(InternalHooks).onUserRetrievedAllWorkflows({ - user_id: req.user.id, - public_api: true, + Container.get(EventService).emit('user-retrieved-all-workflows', { + userId: req.user.id, + publicApi: true, }); return res.json({ diff --git a/packages/cli/src/auth/methods/ldap.ts b/packages/cli/src/auth/methods/ldap.ts index c632557c95..66f6f6dcd9 100644 --- a/packages/cli/src/auth/methods/ldap.ts +++ b/packages/cli/src/auth/methods/ldap.ts @@ -1,6 +1,5 @@ import { Container } from 'typedi'; -import { InternalHooks } from '@/InternalHooks'; import { LdapService } from '@/Ldap/ldap.service.ee'; import { createLdapUserOnLocalDb, @@ -51,11 +50,11 @@ export const handleLdapLogin = async ( await updateLdapUserOnLocalDb(identity, ldapAttributesValues); } else { const user = await createLdapUserOnLocalDb(ldapAttributesValues, ldapId); - Container.get(InternalHooks).onUserSignup(user, { - user_type: 'ldap', - was_disabled_ldap_user: false, + Container.get(EventService).emit('user-signed-up', { + user, + userType: 'ldap', + wasDisabledLdapUser: false, }); - Container.get(EventService).emit('user-signed-up', { user }); return user; } } else { diff --git a/packages/cli/src/controllers/__tests__/me.controller.test.ts b/packages/cli/src/controllers/__tests__/me.controller.test.ts index 9fd165be80..6e187f9cd5 100644 --- a/packages/cli/src/controllers/__tests__/me.controller.test.ts +++ b/packages/cli/src/controllers/__tests__/me.controller.test.ts @@ -13,6 +13,7 @@ import { InternalHooks } from '@/InternalHooks'; import { License } from '@/License'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { UserRepository } from '@/databases/repositories/user.repository'; +import { EventService } from '@/events/event.service'; import { badPasswords } from '@test/testData'; import { mockInstance } from '@test/mocking'; @@ -20,7 +21,8 @@ const browserId = 'test-browser-id'; describe('MeController', () => { const externalHooks = mockInstance(ExternalHooks); - const internalHooks = mockInstance(InternalHooks); + mockInstance(InternalHooks); + const eventService = mockInstance(EventService); const userService = mockInstance(UserService); const userRepository = mockInstance(UserRepository); mockInstance(License).isWithinUsersLimit.mockReturnValue(true); @@ -202,9 +204,9 @@ describe('MeController', () => { req.user.password, ]); - expect(internalHooks.onUserUpdate).toHaveBeenCalledWith({ + expect(eventService.emit).toHaveBeenCalledWith('user-updated', { user: req.user, - fields_changed: ['password'], + fieldsChanged: ['password'], }); }); }); diff --git a/packages/cli/src/controllers/invitation.controller.ts b/packages/cli/src/controllers/invitation.controller.ts index edf3b5c151..19bd803c5e 100644 --- a/packages/cli/src/controllers/invitation.controller.ts +++ b/packages/cli/src/controllers/invitation.controller.ts @@ -16,7 +16,6 @@ import type { User } from '@/databases/entities/User'; import { UserRepository } from '@db/repositories/user.repository'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; -import { InternalHooks } from '@/InternalHooks'; import { ExternalHooks } from '@/ExternalHooks'; import { EventService } from '@/events/event.service'; @@ -24,7 +23,6 @@ import { EventService } from '@/events/event.service'; export class InvitationController { constructor( private readonly logger: Logger, - private readonly internalHooks: InternalHooks, private readonly externalHooks: ExternalHooks, private readonly authService: AuthService, private readonly userService: UserService, @@ -168,11 +166,11 @@ export class InvitationController { this.authService.issueCookie(res, updatedUser, req.browserId); - this.internalHooks.onUserSignup(updatedUser, { - user_type: 'email', - was_disabled_ldap_user: false, + this.eventService.emit('user-signed-up', { + user: updatedUser, + userType: 'email', + wasDisabledLdapUser: false, }); - this.eventService.emit('user-signed-up', { user: updatedUser }); const publicInvitee = await this.userService.toPublic(invitee); diff --git a/packages/cli/src/controllers/me.controller.ts b/packages/cli/src/controllers/me.controller.ts index 39dfc93ab7..2073f737c4 100644 --- a/packages/cli/src/controllers/me.controller.ts +++ b/packages/cli/src/controllers/me.controller.ts @@ -19,7 +19,6 @@ import { isSamlLicensedAndEnabled } from '@/sso/saml/samlHelpers'; import { UserService } from '@/services/user.service'; import { Logger } from '@/Logger'; import { ExternalHooks } from '@/ExternalHooks'; -import { InternalHooks } from '@/InternalHooks'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { UserRepository } from '@/databases/repositories/user.repository'; import { isApiEnabled } from '@/PublicApi'; @@ -40,7 +39,6 @@ export class MeController { constructor( private readonly logger: Logger, private readonly externalHooks: ExternalHooks, - private readonly internalHooks: InternalHooks, private readonly authService: AuthService, private readonly userService: UserService, private readonly passwordUtility: PasswordUtility, @@ -101,7 +99,6 @@ export class MeController { this.authService.issueCookie(res, user, req.browserId); const fieldsChanged = Object.keys(payload); - this.internalHooks.onUserUpdate({ user, fields_changed: fieldsChanged }); this.eventService.emit('user-updated', { user, fieldsChanged }); const publicUser = await this.userService.toPublic(user); @@ -151,7 +148,6 @@ export class MeController { this.authService.issueCookie(res, updatedUser, req.browserId); - 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 +182,10 @@ export class MeController { this.logger.info('User survey updated successfully', { userId: req.user.id }); - this.internalHooks.onPersonalizationSurveySubmitted(req.user.id, personalizationAnswers); + this.eventService.emit('user-submitted-personalization-survey', { + userId: req.user.id, + answers: personalizationAnswers, + }); return { success: true }; } diff --git a/packages/cli/src/controllers/passwordReset.controller.ts b/packages/cli/src/controllers/passwordReset.controller.ts index 84d3b40124..82506e952b 100644 --- a/packages/cli/src/controllers/passwordReset.controller.ts +++ b/packages/cli/src/controllers/passwordReset.controller.ts @@ -215,17 +215,16 @@ export class PasswordResetController { this.authService.issueCookie(res, user, req.browserId); - this.internalHooks.onUserUpdate({ user, fields_changed: ['password'] }); this.eventService.emit('user-updated', { user, fieldsChanged: ['password'] }); - // if this user used to be an LDAP users + // if this user used to be an LDAP user const ldapIdentity = user?.authIdentities?.find((i) => i.providerType === 'ldap'); if (ldapIdentity) { - this.internalHooks.onUserSignup(user, { - user_type: 'email', - was_disabled_ldap_user: true, + this.eventService.emit('user-signed-up', { + user, + userType: 'email', + wasDisabledLdapUser: true, }); - this.eventService.emit('user-signed-up', { user }); } await this.externalHooks.run('user.password.update', [user.email, passwordHash]); diff --git a/packages/cli/src/controllers/users.controller.ts b/packages/cli/src/controllers/users.controller.ts index aee2fd28c2..c834d2cc23 100644 --- a/packages/cli/src/controllers/users.controller.ts +++ b/packages/cli/src/controllers/users.controller.ts @@ -9,7 +9,7 @@ import { UserRoleChangePayload, UserSettingsUpdatePayload, } from '@/requests'; -import type { PublicUser, ITelemetryUserDeletionData } from '@/Interfaces'; +import type { PublicUser } from '@/Interfaces'; import { AuthIdentity } from '@db/entities/AuthIdentity'; import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; @@ -21,7 +21,6 @@ import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ExternalHooks } from '@/ExternalHooks'; -import { InternalHooks } from '@/InternalHooks'; import { validateEntity } from '@/GenericHelpers'; import { ProjectRepository } from '@/databases/repositories/project.repository'; import { Project } from '@/databases/entities/Project'; @@ -35,7 +34,6 @@ export class UsersController { constructor( private readonly logger: Logger, private readonly externalHooks: ExternalHooks, - private readonly internalHooks: InternalHooks, private readonly sharedCredentialsRepository: SharedCredentialsRepository, private readonly sharedWorkflowRepository: SharedWorkflowRepository, private readonly userRepository: UserRepository, @@ -183,12 +181,7 @@ export class UsersController { ); } - const telemetryData: ITelemetryUserDeletionData = { - user_id: req.user.id, - target_user_old_status: userToDelete.isPending ? 'invited' : 'active', - target_user_id: idToDelete, - migration_strategy: transferId ? 'transfer_data' : 'delete_data', - }; + let transfereeId; if (transferId) { const transfereePersonalProject = await this.projectRepository.findOneBy({ id: transferId }); @@ -206,7 +199,7 @@ export class UsersController { }, }); - telemetryData.migration_user_id = transferee.id; + transfereeId = transferee.id; await this.userService.getManager().transaction(async (trx) => { await this.workflowService.transferAll( @@ -253,12 +246,14 @@ export class UsersController { await trx.delete(User, { id: userToDelete.id }); }); - this.internalHooks.onUserDeletion({ + this.eventService.emit('user-deleted', { user: req.user, - telemetryData, publicApi: false, + targetUserOldStatus: userToDelete.isPending ? 'invited' : 'active', + targetUserId: idToDelete, + migrationStrategy: transferId ? 'transfer_data' : 'delete_data', + migrationUserId: transfereeId, }); - this.eventService.emit('user-deleted', { user: req.user }); await this.externalHooks.run('user.deleted', [await this.userService.toPublic(userToDelete)]); @@ -294,11 +289,11 @@ export class UsersController { await this.userService.update(targetUser.id, { role: payload.newRoleName }); - this.internalHooks.onUserRoleChange({ - user: req.user, - target_user_id: targetUser.id, - target_user_new_role: ['global', payload.newRoleName].join(' '), - public_api: false, + this.eventService.emit('user-changed-role', { + userId: req.user.id, + targetUserId: targetUser.id, + targetUserNewRole: ['global', payload.newRoleName].join(' '), + publicApi: false, }); const projects = await this.projectService.getUserOwnedOrAdminProjects(targetUser.id); diff --git a/packages/cli/src/events/__tests__/log-streaming-event-relay.test.ts b/packages/cli/src/events/__tests__/log-streaming-event-relay.test.ts index 4084bebeb2..ebd56ee514 100644 --- a/packages/cli/src/events/__tests__/log-streaming-event-relay.test.ts +++ b/packages/cli/src/events/__tests__/log-streaming-event-relay.test.ts @@ -242,6 +242,11 @@ describe('LogStreamingEventRelay', () => { lastName: 'Doe', role: 'some-role', }, + targetUserOldStatus: 'active', + publicApi: false, + migrationStrategy: 'transfer_data', + targetUserId: '456', + migrationUserId: '789', }; eventService.emit('user-deleted', event); diff --git a/packages/cli/src/events/relay-event-map.ts b/packages/cli/src/events/relay-event-map.ts index 193e85c9a4..0d329508b4 100644 --- a/packages/cli/src/events/relay-event-map.ts +++ b/packages/cli/src/events/relay-event-map.ts @@ -2,6 +2,7 @@ import type { AuthenticationMethod, IRun, IWorkflowBase } from 'n8n-workflow'; import type { IWorkflowDb, IWorkflowExecutionDataProcess } from '@/Interfaces'; import type { ProjectRole } from '@/databases/entities/ProjectRelation'; import type { GlobalRole } from '@/databases/entities/User'; +import type { AuthProviderType } from '@/databases/entities/AuthIdentity'; export type UserLike = { id: string; @@ -72,13 +73,26 @@ export type RelayEventMap = { // #region User + 'user-submitted-personalization-survey': { + userId: string; + answers: Record; + }; + 'user-deleted': { user: UserLike; + publicApi: boolean; + targetUserOldStatus: 'active' | 'invited'; + migrationStrategy?: 'transfer_data' | 'delete_data'; + targetUserId?: string; + migrationUserId?: string; }; 'user-invited': { user: UserLike; targetUserId: string[]; + publicApi: boolean; + emailSent: boolean; + inviteeRole: string; }; 'user-reinvited': { @@ -93,6 +107,8 @@ export type RelayEventMap = { 'user-signed-up': { user: UserLike; + userType: AuthProviderType; + wasDisabledLdapUser: boolean; }; 'user-logged-in': { @@ -106,6 +122,43 @@ export type RelayEventMap = { reason?: string; }; + 'user-changed-role': { + userId: string; + targetUserId: string; + publicApi: boolean; + targetUserNewRole: string; + }; + + 'user-retrieved-user': { + userId: string; + publicApi: boolean; + }; + + 'user-retrieved-all-users': { + userId: string; + publicApi: boolean; + }; + + 'user-retrieved-execution': { + userId: string; + publicApi: boolean; + }; + + 'user-retrieved-all-executions': { + userId: string; + publicApi: boolean; + }; + + 'user-retrieved-workflow': { + userId: string; + publicApi: boolean; + }; + + 'user-retrieved-all-workflows': { + userId: string; + publicApi: boolean; + }; + // #endregion // #region Click diff --git a/packages/cli/src/events/telemetry-event-relay.ts b/packages/cli/src/events/telemetry-event-relay.ts index 91f82a0c12..df4e577acf 100644 --- a/packages/cli/src/events/telemetry-event-relay.ts +++ b/packages/cli/src/events/telemetry-event-relay.ts @@ -17,6 +17,7 @@ import { ProjectRelationRepository } from '@/databases/repositories/projectRelat import type { IExecutionTrackProperties } from '@/Interfaces'; import { determineFinalExecutionStatus } from '@/executionLifecycleHooks/shared/sharedHookFunctions'; import { EventRelay } from './event-relay'; +import { snakeCase } from 'change-case'; @Service() export class TelemetryEventRelay extends EventRelay { @@ -73,6 +74,19 @@ export class TelemetryEventRelay extends EventRelay { 'workflow-saved': async (event) => await this.workflowSaved(event), 'server-started': async () => await this.serverStarted(), 'workflow-post-execute': async (event) => await this.workflowPostExecute(event), + 'user-changed-role': (event) => this.userChangedRole(event), + 'user-retrieved-user': (event) => this.userRetrievedUser(event), + 'user-retrieved-all-users': (event) => this.userRetrievedAllUsers(event), + 'user-retrieved-execution': (event) => this.userRetrievedExecution(event), + 'user-retrieved-all-executions': (event) => this.userRetrievedAllExecutions(event), + 'user-retrieved-workflow': (event) => this.userRetrievedWorkflow(event), + 'user-retrieved-all-workflows': (event) => this.userRetrievedAllWorkflows(event), + 'user-updated': (event) => this.userUpdated(event), + 'user-deleted': (event) => this.userDeleted(event), + 'user-invited': (event) => this.userInvited(event), + 'user-signed-up': (event) => this.userSignedUp(event), + 'user-submitted-personalization-survey': (event) => + this.userSubmittedPersonalizationSurvey(event), }); } @@ -744,4 +758,132 @@ export class TelemetryEventRelay extends EventRelay { } // #endregion + + // #region User + + private userChangedRole({ + userId, + targetUserId, + targetUserNewRole, + publicApi, + }: RelayEventMap['user-changed-role']) { + this.telemetry.track('User changed role', { + user_id: userId, + target_user_id: targetUserId, + target_user_new_role: targetUserNewRole, + public_api: publicApi, + }); + } + + private userRetrievedUser({ userId, publicApi }: RelayEventMap['user-retrieved-user']) { + this.telemetry.track('User retrieved user', { + user_id: userId, + public_api: publicApi, + }); + } + + private userRetrievedAllUsers({ userId, publicApi }: RelayEventMap['user-retrieved-all-users']) { + this.telemetry.track('User retrieved all users', { + user_id: userId, + public_api: publicApi, + }); + } + + private userRetrievedExecution({ userId, publicApi }: RelayEventMap['user-retrieved-execution']) { + this.telemetry.track('User retrieved execution', { + user_id: userId, + public_api: publicApi, + }); + } + + private userRetrievedAllExecutions({ + userId, + publicApi, + }: RelayEventMap['user-retrieved-all-executions']) { + this.telemetry.track('User retrieved all executions', { + user_id: userId, + public_api: publicApi, + }); + } + + private userRetrievedWorkflow({ userId, publicApi }: RelayEventMap['user-retrieved-workflow']) { + this.telemetry.track('User retrieved workflow', { + user_id: userId, + public_api: publicApi, + }); + } + + private userRetrievedAllWorkflows({ + userId, + publicApi, + }: RelayEventMap['user-retrieved-all-workflows']) { + this.telemetry.track('User retrieved all workflows', { + user_id: userId, + public_api: publicApi, + }); + } + + private userUpdated({ user, fieldsChanged }: RelayEventMap['user-updated']) { + this.telemetry.track('User changed personal settings', { + user_id: user.id, + fields_changed: fieldsChanged, + }); + } + + private userDeleted({ + user, + publicApi, + targetUserOldStatus, + migrationStrategy, + targetUserId, + migrationUserId, + }: RelayEventMap['user-deleted']) { + this.telemetry.track('User deleted user', { + user_id: user.id, + public_api: publicApi, + target_user_old_status: targetUserOldStatus, + migration_strategy: migrationStrategy, + target_user_id: targetUserId, + migration_user_id: migrationUserId, + }); + } + + private userInvited({ + user, + targetUserId, + publicApi, + emailSent, + inviteeRole, + }: RelayEventMap['user-invited']) { + this.telemetry.track('User invited new user', { + user_id: user.id, + target_user_id: targetUserId, + public_api: publicApi, + email_sent: emailSent, + invitee_role: inviteeRole, + }); + } + + private userSignedUp({ user, userType, wasDisabledLdapUser }: RelayEventMap['user-signed-up']) { + this.telemetry.track('User signed up', { + user_id: user.id, + user_type: userType, + was_disabled_ldap_user: wasDisabledLdapUser, + }); + } + + private userSubmittedPersonalizationSurvey({ + userId, + answers, + }: RelayEventMap['user-submitted-personalization-survey']) { + const camelCaseKeys = Object.keys(answers); + const personalizationSurveyData = { user_id: userId } as Record; + camelCaseKeys.forEach((camelCaseKey) => { + personalizationSurveyData[snakeCase(camelCaseKey)] = answers[camelCaseKey]; + }); + + this.telemetry.track('User responded to personalization questions', personalizationSurveyData); + } + + // #endregion } diff --git a/packages/cli/src/services/user.service.ts b/packages/cli/src/services/user.service.ts index 25be080ca0..61f76bd596 100644 --- a/packages/cli/src/services/user.service.ts +++ b/packages/cli/src/services/user.service.ts @@ -151,16 +151,12 @@ export class UserService { }); } - Container.get(InternalHooks).onUserInvite({ - user: owner, - target_user_id: Object.values(toInviteUsers), - public_api: false, - email_sent: result.emailSent, - invitee_role: role, // same role for all invited users - }); this.eventService.emit('user-invited', { user: owner, targetUserId: Object.values(toInviteUsers), + publicApi: false, + emailSent: result.emailSent, + inviteeRole: role, // same role for all invited users }); } catch (e) { if (e instanceof Error) {