refactor(core): Clean up event relays (no-changelog) (#10284)

This commit is contained in:
Iván Ovejero 2024-08-02 16:52:49 +02:00 committed by GitHub
parent 55f2ffe256
commit aa0a470dce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 487 additions and 397 deletions

View file

@ -13,7 +13,7 @@ import { Logger } from '@/Logger';
import { jsonParse, type IDataObject, ApplicationError } from 'n8n-workflow'; import { jsonParse, type IDataObject, ApplicationError } from 'n8n-workflow';
import { EXTERNAL_SECRETS_INITIAL_BACKOFF, EXTERNAL_SECRETS_MAX_BACKOFF } from './constants'; import { EXTERNAL_SECRETS_INITIAL_BACKOFF, EXTERNAL_SECRETS_MAX_BACKOFF } from './constants';
import { License } from '@/License'; import { License } from '@/License';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
import { updateIntervalTime } from './externalSecretsHelper.ee'; import { updateIntervalTime } from './externalSecretsHelper.ee';
import { ExternalSecretsProviders } from './ExternalSecretsProviders.ee'; import { ExternalSecretsProviders } from './ExternalSecretsProviders.ee';
import { OrchestrationService } from '@/services/orchestration.service'; import { OrchestrationService } from '@/services/orchestration.service';

View file

@ -9,8 +9,8 @@ import { Telemetry } from '@/telemetry';
import { MessageEventBus } from './eventbus/MessageEventBus/MessageEventBus'; import { MessageEventBus } from './eventbus/MessageEventBus/MessageEventBus';
/** /**
* @deprecated Do not add to this class. To add audit or telemetry events, use * @deprecated Do not add to this class. To add log streaming or telemetry events, use
* `EventService` to emit the event and then use the `AuditEventRelay` or * `EventService` to emit the event and then use the `LogStreamingEventRelay` or
* `TelemetryEventRelay` to forward them to the event bus or telemetry. * `TelemetryEventRelay` to forward them to the event bus or telemetry.
*/ */
@Service() @Service()

View file

@ -6,7 +6,7 @@ import { NON_SENSIBLE_LDAP_CONFIG_PROPERTIES } from './constants';
import { getLdapSynchronizations } from './helpers.ee'; import { getLdapSynchronizations } from './helpers.ee';
import { LdapConfiguration } from './types'; import { LdapConfiguration } from './types';
import { LdapService } from './ldap.service.ee'; import { LdapService } from './ldap.service.ee';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
@RestController('/ldap') @RestController('/ldap')
export class LdapController { export class LdapController {

View file

@ -44,7 +44,7 @@ import {
LDAP_LOGIN_ENABLED, LDAP_LOGIN_ENABLED,
LDAP_LOGIN_LABEL, LDAP_LOGIN_LABEL,
} from './constants'; } from './constants';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
@Service() @Service()
export class LdapService { export class LdapService {

View file

@ -15,7 +15,7 @@ import { UserRepository } from '@db/repositories/user.repository';
import { UrlService } from '@/services/url.service'; import { UrlService } from '@/services/url.service';
import type { AuthenticatedRequest } from '@/requests'; import type { AuthenticatedRequest } from '@/requests';
import { GlobalConfig } from '@n8n/config'; import { GlobalConfig } from '@n8n/config';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
async function createApiRouter( async function createApiRouter(
version: string, version: string,

View file

@ -17,7 +17,7 @@ import { Container } from 'typedi';
import { CredentialsRepository } from '@db/repositories/credentials.repository'; import { CredentialsRepository } from '@db/repositories/credentials.repository';
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
import { ProjectRepository } from '@/databases/repositories/project.repository'; import { ProjectRepository } from '@/databases/repositories/project.repository';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
export async function getCredentials(credentialId: string): Promise<ICredentialsDb | null> { export async function getCredentials(credentialId: string): Promise<ICredentialsDb | null> {
return await Container.get(CredentialsRepository).findOneBy({ id: credentialId }); return await Container.get(CredentialsRepository).findOneBy({ id: credentialId });

View file

@ -10,7 +10,7 @@ import {
getTrackingInformationFromPullResult, getTrackingInformationFromPullResult,
isSourceControlLicensed, isSourceControlLicensed,
} from '@/environments/sourceControl/sourceControlHelper.ee'; } from '@/environments/sourceControl/sourceControlHelper.ee';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
export = { export = {
pull: [ pull: [

View file

@ -32,7 +32,7 @@ import { SharedWorkflowRepository } from '@/databases/repositories/sharedWorkflo
import { TagRepository } from '@/databases/repositories/tag.repository'; import { TagRepository } from '@/databases/repositories/tag.repository';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import { ProjectRepository } from '@/databases/repositories/project.repository'; import { ProjectRepository } from '@/databases/repositories/project.repository';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
import { z } from 'zod'; import { z } from 'zod';
import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee'; import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee';

View file

@ -35,7 +35,7 @@ import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
import { handleMfaDisable, isMfaFeatureEnabled } from '@/Mfa/helpers'; import { handleMfaDisable, isMfaFeatureEnabled } from '@/Mfa/helpers';
import type { FrontendService } from '@/services/frontend.service'; import type { FrontendService } from '@/services/frontend.service';
import { OrchestrationService } from '@/services/orchestration.service'; import { OrchestrationService } from '@/services/orchestration.service';
import { AuditEventRelay } from './eventbus/audit-event-relay.service'; import { LogStreamingEventRelay } from '@/events/log-streaming-event-relay';
import '@/controllers/activeWorkflows.controller'; import '@/controllers/activeWorkflows.controller';
import '@/controllers/auth.controller'; import '@/controllers/auth.controller';
@ -64,7 +64,7 @@ import '@/ExternalSecrets/ExternalSecrets.controller.ee';
import '@/license/license.controller'; import '@/license/license.controller';
import '@/workflows/workflowHistory/workflowHistory.controller.ee'; import '@/workflows/workflowHistory/workflowHistory.controller.ee';
import '@/workflows/workflows.controller'; import '@/workflows/workflows.controller';
import { EventService } from './eventbus/event.service'; import { EventService } from './events/event.service';
const exec = promisify(callbackExec); const exec = promisify(callbackExec);
@ -250,7 +250,7 @@ export class Server extends AbstractServer {
// ---------------------------------------- // ----------------------------------------
const eventBus = Container.get(MessageEventBus); const eventBus = Container.get(MessageEventBus);
await eventBus.initialize(); await eventBus.initialize();
Container.get(AuditEventRelay).init(); Container.get(LogStreamingEventRelay).init();
if (this.endpointPresetCredentials !== '') { if (this.endpointPresetCredentials !== '') {
// POST endpoint to set preset credentials // POST endpoint to set preset credentials

View file

@ -16,7 +16,7 @@ import { toError } from '@/utils';
import type { InviteEmailData, PasswordResetData, SendEmailResult } from './Interfaces'; import type { InviteEmailData, PasswordResetData, SendEmailResult } from './Interfaces';
import { NodeMailer } from './NodeMailer'; import { NodeMailer } from './NodeMailer';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
type Template = HandlebarsTemplateDelegate<unknown>; type Template = HandlebarsTemplateDelegate<unknown>;
type TemplateName = 'invite' | 'passwordReset' | 'workflowShared' | 'credentialsShared'; type TemplateName = 'invite' | 'passwordReset' | 'workflowShared' | 'credentialsShared';

View file

@ -70,7 +70,7 @@ import { WorkflowRepository } from './databases/repositories/workflow.repository
import { UrlService } from './services/url.service'; import { UrlService } from './services/url.service';
import { WorkflowExecutionService } from './workflows/workflowExecution.service'; import { WorkflowExecutionService } from './workflows/workflowExecution.service';
import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
import { EventService } from './eventbus/event.service'; import { EventService } from './events/event.service';
import { GlobalConfig } from '@n8n/config'; import { GlobalConfig } from '@n8n/config';
import { SubworkflowPolicyChecker } from './subworkflows/subworkflow-policy-checker.service'; import { SubworkflowPolicyChecker } from './subworkflows/subworkflow-policy-checker.service';

View file

@ -36,7 +36,7 @@ import { generateFailedExecutionFromError } from '@/WorkflowHelpers';
import { PermissionChecker } from '@/UserManagement/PermissionChecker'; import { PermissionChecker } from '@/UserManagement/PermissionChecker';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { WorkflowStaticDataService } from '@/workflows/workflowStaticData.service'; import { WorkflowStaticDataService } from '@/workflows/workflowStaticData.service';
import { EventService } from './eventbus/event.service'; import { EventService } from './events/event.service';
@Service() @Service()
export class WorkflowRunner { export class WorkflowRunner {

View file

@ -4,7 +4,7 @@ import { Container } from 'typedi';
import { isLdapLoginEnabled } from '@/Ldap/helpers.ee'; import { isLdapLoginEnabled } from '@/Ldap/helpers.ee';
import { UserRepository } from '@db/repositories/user.repository'; import { UserRepository } from '@db/repositories/user.repository';
import { AuthError } from '@/errors/response-errors/auth.error'; import { AuthError } from '@/errors/response-errors/auth.error';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
export const handleEmailLogin = async ( export const handleEmailLogin = async (
email: string, email: string,

View file

@ -12,7 +12,7 @@ import {
updateLdapUserOnLocalDb, updateLdapUserOnLocalDb,
} from '@/Ldap/helpers.ee'; } from '@/Ldap/helpers.ee';
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
export const handleLdapLogin = async ( export const handleLdapLogin = async (
loginId: string, loginId: string,

View file

@ -23,7 +23,7 @@ import { initExpressionEvaluator } from '@/ExpressionEvaluator';
import { generateHostInstanceId } from '@db/utils/generators'; import { generateHostInstanceId } from '@db/utils/generators';
import { WorkflowHistoryManager } from '@/workflows/workflowHistory/workflowHistoryManager.ee'; import { WorkflowHistoryManager } from '@/workflows/workflowHistory/workflowHistoryManager.ee';
import { ShutdownService } from '@/shutdown/Shutdown.service'; import { ShutdownService } from '@/shutdown/Shutdown.service';
import { TelemetryEventRelay } from '@/telemetry/telemetry-event-relay.service'; import { TelemetryEventRelay } from '@/events/telemetry-event-relay';
export abstract class BaseCommand extends Command { export abstract class BaseCommand extends Command {
protected logger = Container.get(Logger); protected logger = Container.get(Logger);

View file

@ -33,7 +33,7 @@ import { ExecutionService } from '@/executions/execution.service';
import { OwnershipService } from '@/services/ownership.service'; import { OwnershipService } from '@/services/ownership.service';
import { WorkflowRunner } from '@/WorkflowRunner'; import { WorkflowRunner } from '@/WorkflowRunner';
import { ExecutionRecoveryService } from '@/executions/execution-recovery.service'; import { ExecutionRecoveryService } from '@/executions/execution-recovery.service';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
const open = require('open'); const open = require('open');

View file

@ -30,7 +30,7 @@ import type { WorkerJobStatusSummary } from '@/services/orchestration/worker/typ
import { ServiceUnavailableError } from '@/errors/response-errors/service-unavailable.error'; import { ServiceUnavailableError } from '@/errors/response-errors/service-unavailable.error';
import { BaseCommand } from './BaseCommand'; import { BaseCommand } from './BaseCommand';
import { MaxStalledCountError } from '@/errors/max-stalled-count.error'; import { MaxStalledCountError } from '@/errors/max-stalled-count.error';
import { AuditEventRelay } from '@/eventbus/audit-event-relay.service'; import { LogStreamingEventRelay } from '@/events/log-streaming-event-relay';
export class Worker extends BaseCommand { export class Worker extends BaseCommand {
static description = '\nStarts a n8n worker'; static description = '\nStarts a n8n worker';
@ -286,7 +286,7 @@ export class Worker extends BaseCommand {
await Container.get(MessageEventBus).initialize({ await Container.get(MessageEventBus).initialize({
workerId: this.queueModeId, workerId: this.queueModeId,
}); });
Container.get(AuditEventRelay).init(); Container.get(LogStreamingEventRelay).init();
} }
/** /**

View file

@ -12,7 +12,7 @@ import type { WorkflowExecuteMode as ExecutionMode } from 'n8n-workflow';
import type { ExecutionRepository } from '@/databases/repositories/execution.repository'; import type { ExecutionRepository } from '@/databases/repositories/execution.repository';
import type { IExecutingWorkflowData } from '@/Interfaces'; import type { IExecutingWorkflowData } from '@/Interfaces';
import type { Telemetry } from '@/telemetry'; import type { Telemetry } from '@/telemetry';
import type { EventService } from '@/eventbus/event.service'; import type { EventService } from '@/events/event.service';
describe('ConcurrencyControlService', () => { describe('ConcurrencyControlService', () => {
const logger = mock<Logger>(); const logger = mock<Logger>();

View file

@ -8,7 +8,7 @@ import { ExecutionRepository } from '@/databases/repositories/execution.reposito
import type { WorkflowExecuteMode as ExecutionMode } from 'n8n-workflow'; import type { WorkflowExecuteMode as ExecutionMode } from 'n8n-workflow';
import type { IExecutingWorkflowData } from '@/Interfaces'; import type { IExecutingWorkflowData } from '@/Interfaces';
import { Telemetry } from '@/telemetry'; import { Telemetry } from '@/telemetry';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
export const CLOUD_TEMP_PRODUCTION_LIMIT = 999; export const CLOUD_TEMP_PRODUCTION_LIMIT = 999;
export const CLOUD_TEMP_REPORTABLE_THRESHOLDS = [5, 10, 20, 50, 100, 200]; export const CLOUD_TEMP_REPORTABLE_THRESHOLDS = [5, 10, 20, 50, 100, 200];

View file

@ -24,7 +24,7 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
import { ApplicationError } from 'n8n-workflow'; import { ApplicationError } from 'n8n-workflow';
import { UserRepository } from '@/databases/repositories/user.repository'; import { UserRepository } from '@/databases/repositories/user.repository';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
@RestController() @RestController()
export class AuthController { export class AuthController {

View file

@ -13,7 +13,7 @@ import { Push } from '@/push';
import { CommunityPackagesService } from '@/services/communityPackages.service'; import { CommunityPackagesService } from '@/services/communityPackages.service';
import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { InternalServerError } from '@/errors/response-errors/internal-server.error'; import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
const { const {
PACKAGE_NOT_INSTALLED, PACKAGE_NOT_INSTALLED,

View file

@ -18,7 +18,7 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
import { InternalHooks } from '@/InternalHooks'; import { InternalHooks } from '@/InternalHooks';
import { ExternalHooks } from '@/ExternalHooks'; import { ExternalHooks } from '@/ExternalHooks';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
@RestController('/invitations') @RestController('/invitations')
export class InvitationController { export class InvitationController {

View file

@ -23,7 +23,7 @@ import { InternalHooks } from '@/InternalHooks';
import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { UserRepository } from '@/databases/repositories/user.repository'; import { UserRepository } from '@/databases/repositories/user.repository';
import { isApiEnabled } from '@/PublicApi'; import { isApiEnabled } from '@/PublicApi';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
export const API_KEY_PREFIX = 'n8n_api_'; export const API_KEY_PREFIX = 'n8n_api_';

View file

@ -21,7 +21,7 @@ import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error';
import { UnprocessableRequestError } from '@/errors/response-errors/unprocessable.error'; import { UnprocessableRequestError } from '@/errors/response-errors/unprocessable.error';
import { UserRepository } from '@/databases/repositories/user.repository'; import { UserRepository } from '@/databases/repositories/user.repository';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
@RestController() @RestController()
export class PasswordResetController { export class PasswordResetController {

View file

@ -23,7 +23,7 @@ import { ProjectRepository } from '@/databases/repositories/project.repository';
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import // eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
import { In, Not } from '@n8n/typeorm'; import { In, Not } from '@n8n/typeorm';
import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
@RestController('/projects') @RestController('/projects')
export class ProjectController { export class ProjectController {

View file

@ -28,7 +28,7 @@ import { Project } from '@/databases/entities/Project';
import { WorkflowService } from '@/workflows/workflow.service'; import { WorkflowService } from '@/workflows/workflow.service';
import { CredentialsService } from '@/credentials/credentials.service'; import { CredentialsService } from '@/credentials/credentials.service';
import { ProjectService } from '@/services/project.service'; import { ProjectService } from '@/services/project.service';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
@RestController('/users') @RestController('/users')
export class UsersController { export class UsersController {

View file

@ -30,7 +30,7 @@ import { SharedCredentialsRepository } from '@/databases/repositories/sharedCred
import { SharedCredentials } from '@/databases/entities/SharedCredentials'; import { SharedCredentials } from '@/databases/entities/SharedCredentials';
import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository'; import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository';
import { z } from 'zod'; import { z } from 'zod';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
@RestController('/credentials') @RestController('/credentials')
export class CredentialsController { export class CredentialsController {

View file

@ -1,5 +1,5 @@
import { RedactableError } from '@/errors/redactable.error'; import { RedactableError } from '@/errors/redactable.error';
import type { UserLike } from '@/eventbus/event.types'; import type { UserLike } from '@/events/relay-event-map';
function toRedactable(userLike: UserLike) { function toRedactable(userLike: UserLike) {
return { return {
@ -14,7 +14,7 @@ function toRedactable(userLike: UserLike) {
type FieldName = 'user' | 'inviter' | 'invitee'; type FieldName = 'user' | 'inviter' | 'invitee';
/** /**
* Mark redactable properties in a `{ user: UserLike }` field in an `AuditEventRelay` * Mark redactable properties in a `{ user: UserLike }` field in an `LogStreamingEventRelay`
* method arg. These properties will be later redacted by the log streaming * method arg. These properties will be later redacted by the log streaming
* destination based on user prefs. Only for `n8n.audit.*` logs. * destination based on user prefs. Only for `n8n.audit.*` logs.
* *

View file

@ -12,7 +12,7 @@ import type { SourceControlPreferences } from './types/sourceControlPreferences'
import type { SourceControlledFile } from './types/sourceControlledFile'; import type { SourceControlledFile } from './types/sourceControlledFile';
import { SOURCE_CONTROL_DEFAULT_BRANCH } from './constants'; import { SOURCE_CONTROL_DEFAULT_BRANCH } from './constants';
import type { ImportResult } from './types/importResult'; import type { ImportResult } from './types/importResult';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
import { getRepoType } from './sourceControlHelper.ee'; import { getRepoType } from './sourceControlHelper.ee';
import { SourceControlGetStatus } from './types/sourceControlGetStatus'; import { SourceControlGetStatus } from './types/sourceControlGetStatus';
import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { BadRequestError } from '@/errors/response-errors/bad-request.error';

View file

@ -30,7 +30,7 @@ import type { TagEntity } from '@db/entities/TagEntity';
import type { Variables } from '@db/entities/Variables'; import type { Variables } from '@db/entities/Variables';
import type { SourceControlWorkflowVersionId } from './types/sourceControlWorkflowVersionId'; import type { SourceControlWorkflowVersionId } from './types/sourceControlWorkflowVersionId';
import type { ExportableCredential } from './types/exportableCredential'; import type { ExportableCredential } from './types/exportableCredential';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
import { TagRepository } from '@db/repositories/tag.repository'; import { TagRepository } from '@db/repositories/tag.repository';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { BadRequestError } from '@/errors/response-errors/bad-request.error';

View file

@ -6,7 +6,7 @@ import { CacheService } from '@/services/cache/cache.service';
import { VariablesRepository } from '@db/repositories/variables.repository'; import { VariablesRepository } from '@db/repositories/variables.repository';
import { VariableCountLimitReachedError } from '@/errors/variable-count-limit-reached.error'; import { VariableCountLimitReachedError } from '@/errors/variable-count-limit-reached.error';
import { VariableValidationError } from '@/errors/variable-validation.error'; import { VariableValidationError } from '@/errors/variable-validation.error';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
@Service() @Service()
export class VariablesService { export class VariablesService {

View file

@ -3,7 +3,7 @@ import { ApplicationError } from 'n8n-workflow';
export class RedactableError extends ApplicationError { export class RedactableError extends ApplicationError {
constructor(fieldName: string, args: string) { constructor(fieldName: string, args: string) {
super( super(
`Failed to find "${fieldName}" property in argument "${args.toString()}". Please set the decorator \`@Redactable()\` only on \`AuditEventRelay\` methods where the argument contains a "${fieldName}" property.`, `Failed to find "${fieldName}" property in argument "${args.toString()}". Please set the decorator \`@Redactable()\` only on \`LogStreamingEventRelay\` methods where the argument contains a "${fieldName}" property.`,
); );
} }
} }

View file

@ -1,6 +0,0 @@
import { Service } from 'typedi';
import { TypedEmitter } from '@/TypedEmitter';
import type { Event } from './event.types';
@Service()
export class EventService extends TypedEmitter<Event> {}

View file

@ -1,16 +1,15 @@
import { mock } from 'jest-mock-extended'; import { mock } from 'jest-mock-extended';
import { AuditEventRelay } from '../audit-event-relay.service'; import { LogStreamingEventRelay } from '@/events/log-streaming-event-relay';
import type { MessageEventBus } from '../MessageEventBus/MessageEventBus'; import { EventService } from '@/events/event.service';
import type { Event } from '../event.types';
import { EventService } from '../event.service';
import type { INode, IRun, IWorkflowBase } from 'n8n-workflow'; import type { INode, IRun, IWorkflowBase } from 'n8n-workflow';
import type { IWorkflowDb } from '@/Interfaces'; import type { IWorkflowDb } from '@/Interfaces';
import type { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
import type { RelayEventMap } from '@/events/relay-event-map';
describe('AuditEventRelay', () => { describe('LogStreamingEventRelay', () => {
const eventBus = mock<MessageEventBus>(); const eventBus = mock<MessageEventBus>();
const eventService = new EventService(); const eventService = new EventService();
const auditor = new AuditEventRelay(eventService, eventBus); new LogStreamingEventRelay(eventService, eventBus).init();
auditor.init();
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@ -18,7 +17,7 @@ describe('AuditEventRelay', () => {
describe('workflow events', () => { describe('workflow events', () => {
it('should log on `workflow-created` event', () => { it('should log on `workflow-created` event', () => {
const event: Event['workflow-created'] = { const event: RelayEventMap['workflow-created'] = {
user: { user: {
id: '123', id: '123',
email: 'john@n8n.io', email: 'john@n8n.io',
@ -52,7 +51,7 @@ describe('AuditEventRelay', () => {
}); });
it('should log on `workflow-deleted` event', () => { it('should log on `workflow-deleted` event', () => {
const event: Event['workflow-deleted'] = { const event: RelayEventMap['workflow-deleted'] = {
user: { user: {
id: '456', id: '456',
email: 'jane@n8n.io', email: 'jane@n8n.io',
@ -80,7 +79,7 @@ describe('AuditEventRelay', () => {
}); });
it('should log on `workflow-saved` event', () => { it('should log on `workflow-saved` event', () => {
const event: Event['workflow-saved'] = { const event: RelayEventMap['workflow-saved'] = {
user: { user: {
id: '789', id: '789',
email: 'alex@n8n.io', email: 'alex@n8n.io',
@ -119,7 +118,7 @@ describe('AuditEventRelay', () => {
settings: {}, settings: {},
}); });
const event: Event['workflow-pre-execute'] = { const event: RelayEventMap['workflow-pre-execute'] = {
executionId: 'exec123', executionId: 'exec123',
data: workflow, data: workflow,
}; };
@ -139,7 +138,7 @@ describe('AuditEventRelay', () => {
}); });
it('should log on `workflow-post-execute` for successful execution', () => { it('should log on `workflow-post-execute` for successful execution', () => {
const payload = mock<Event['workflow-post-execute']>({ const payload = mock<RelayEventMap['workflow-post-execute']>({
executionId: 'some-id', executionId: 'some-id',
userId: 'some-id', userId: 'some-id',
workflow: mock<IWorkflowBase>({ id: 'some-id', name: 'some-name' }), workflow: mock<IWorkflowBase>({ id: 'some-id', name: 'some-name' }),
@ -208,7 +207,7 @@ describe('AuditEventRelay', () => {
describe('user events', () => { describe('user events', () => {
it('should log on `user-updated` event', () => { it('should log on `user-updated` event', () => {
const event: Event['user-updated'] = { const event: RelayEventMap['user-updated'] = {
user: { user: {
id: 'user456', id: 'user456',
email: 'updated@example.com', email: 'updated@example.com',
@ -235,7 +234,7 @@ describe('AuditEventRelay', () => {
}); });
it('should log on `user-deleted` event', () => { it('should log on `user-deleted` event', () => {
const event: Event['user-deleted'] = { const event: RelayEventMap['user-deleted'] = {
user: { user: {
id: '123', id: '123',
email: 'john@n8n.io', email: 'john@n8n.io',
@ -262,7 +261,7 @@ describe('AuditEventRelay', () => {
describe('click events', () => { describe('click events', () => {
it('should log on `user-password-reset-request-click` event', () => { it('should log on `user-password-reset-request-click` event', () => {
const event: Event['user-password-reset-request-click'] = { const event: RelayEventMap['user-password-reset-request-click'] = {
user: { user: {
id: 'user101', id: 'user101',
email: 'user101@example.com', email: 'user101@example.com',
@ -287,7 +286,7 @@ describe('AuditEventRelay', () => {
}); });
it('should log on `user-invite-email-click` event', () => { it('should log on `user-invite-email-click` event', () => {
const event: Event['user-invite-email-click'] = { const event: RelayEventMap['user-invite-email-click'] = {
inviter: { inviter: {
id: '123', id: '123',
email: 'john@n8n.io', email: 'john@n8n.io',
@ -354,7 +353,7 @@ describe('AuditEventRelay', () => {
settings: {}, settings: {},
}); });
const event: Event['node-pre-execute'] = { const event: RelayEventMap['node-pre-execute'] = {
executionId: 'exec456', executionId: 'exec456',
nodeName: 'HTTP Request', nodeName: 'HTTP Request',
workflow, workflow,
@ -399,7 +398,7 @@ describe('AuditEventRelay', () => {
settings: {}, settings: {},
}); });
const event: Event['node-post-execute'] = { const event: RelayEventMap['node-post-execute'] = {
executionId: 'exec789', executionId: 'exec789',
nodeName: 'HTTP Response', nodeName: 'HTTP Response',
workflow, workflow,
@ -422,7 +421,7 @@ describe('AuditEventRelay', () => {
describe('credentials events', () => { describe('credentials events', () => {
it('should log on `credentials-shared` event', () => { it('should log on `credentials-shared` event', () => {
const event: Event['credentials-shared'] = { const event: RelayEventMap['credentials-shared'] = {
user: { user: {
id: 'user123', id: 'user123',
email: 'sharer@example.com', email: 'sharer@example.com',
@ -457,7 +456,7 @@ describe('AuditEventRelay', () => {
}); });
it('should log on `credentials-created` event', () => { it('should log on `credentials-created` event', () => {
const event: Event['credentials-created'] = { const event: RelayEventMap['credentials-created'] = {
user: { user: {
id: 'user123', id: 'user123',
email: 'user@example.com', email: 'user@example.com',
@ -494,7 +493,7 @@ describe('AuditEventRelay', () => {
describe('auth events', () => { describe('auth events', () => {
it('should log on `user-login-failed` event', () => { it('should log on `user-login-failed` event', () => {
const event: Event['user-login-failed'] = { const event: RelayEventMap['user-login-failed'] = {
userEmail: 'user@example.com', userEmail: 'user@example.com',
authenticationMethod: 'email', authenticationMethod: 'email',
reason: 'Invalid password', reason: 'Invalid password',
@ -515,7 +514,7 @@ describe('AuditEventRelay', () => {
describe('community package events', () => { describe('community package events', () => {
it('should log on `community-package-updated` event', () => { it('should log on `community-package-updated` event', () => {
const event: Event['community-package-updated'] = { const event: RelayEventMap['community-package-updated'] = {
user: { user: {
id: 'user202', id: 'user202',
email: 'packageupdater@example.com', email: 'packageupdater@example.com',
@ -552,7 +551,7 @@ describe('AuditEventRelay', () => {
}); });
it('should log on `community-package-installed` event', () => { it('should log on `community-package-installed` event', () => {
const event: Event['community-package-installed'] = { const event: RelayEventMap['community-package-installed'] = {
user: { user: {
id: 'user789', id: 'user789',
email: 'admin@example.com', email: 'admin@example.com',
@ -593,7 +592,7 @@ describe('AuditEventRelay', () => {
describe('email events', () => { describe('email events', () => {
it('should log on `email-failed` event', () => { it('should log on `email-failed` event', () => {
const event: Event['email-failed'] = { const event: RelayEventMap['email-failed'] = {
user: { user: {
id: 'user789', id: 'user789',
email: 'recipient@example.com', email: 'recipient@example.com',
@ -622,7 +621,7 @@ describe('AuditEventRelay', () => {
describe('public API events', () => { describe('public API events', () => {
it('should log on `public-api-key-created` event', () => { it('should log on `public-api-key-created` event', () => {
const event: Event['public-api-key-created'] = { const event: RelayEventMap['public-api-key-created'] = {
user: { user: {
id: 'user101', id: 'user101',
email: 'apiuser@example.com', email: 'apiuser@example.com',
@ -650,7 +649,7 @@ describe('AuditEventRelay', () => {
describe('execution events', () => { describe('execution events', () => {
it('should log on `execution-throttled` event', () => { it('should log on `execution-throttled` event', () => {
const event: Event['execution-throttled'] = { const event: RelayEventMap['execution-throttled'] = {
executionId: 'exec123456', executionId: 'exec123456',
}; };

View file

@ -0,0 +1,20 @@
import { EventService } from './event.service';
import { Service } from 'typedi';
import type { RelayEventMap } from '@/events/relay-event-map';
@Service()
export class EventRelay {
constructor(readonly eventService: EventService) {}
protected setupListeners<EventNames extends keyof RelayEventMap>(map: {
[EventName in EventNames]?: (event: RelayEventMap[EventName]) => void | Promise<void>;
}) {
for (const [eventName, handler] of Object.entries(map) as Array<
[EventNames, (event: RelayEventMap[EventNames]) => void | Promise<void>]
>) {
this.eventService.on(eventName, async (event) => {
await handler(event);
});
}
}
}

View file

@ -0,0 +1,6 @@
import { Service } from 'typedi';
import { TypedEmitter } from '@/TypedEmitter';
import type { RelayEventMap } from './relay-event-map';
@Service()
export class EventService extends TypedEmitter<RelayEventMap> {}

View file

@ -1,72 +1,58 @@
import { Service } from 'typedi'; import { Service } from 'typedi';
import { MessageEventBus } from './MessageEventBus/MessageEventBus'; import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
import { Redactable } from '@/decorators/Redactable'; import { Redactable } from '@/decorators/Redactable';
import { EventService } from './event.service'; import { EventRelay } from '@/events/event-relay';
import type { Event } from './event.types'; import type { RelayEventMap } from '@/events/relay-event-map';
import type { IWorkflowBase } from 'n8n-workflow'; import type { IWorkflowBase } from 'n8n-workflow';
import { EventService } from './event.service';
@Service() @Service()
export class AuditEventRelay { export class LogStreamingEventRelay extends EventRelay {
constructor( constructor(
private readonly eventService: EventService, readonly eventService: EventService,
private readonly eventBus: MessageEventBus, private readonly eventBus: MessageEventBus,
) {} ) {
super(eventService);
}
init() { init() {
this.setupHandlers(); this.setupListeners({
'workflow-created': (event) => this.workflowCreated(event),
'workflow-deleted': (event) => this.workflowDeleted(event),
'workflow-saved': (event) => this.workflowSaved(event),
'workflow-pre-execute': (event) => this.workflowPreExecute(event),
'workflow-post-execute': (event) => this.workflowPostExecute(event),
'node-pre-execute': (event) => this.nodePreExecute(event),
'node-post-execute': (event) => this.nodePostExecute(event),
'user-deleted': (event) => this.userDeleted(event),
'user-invited': (event) => this.userInvited(event),
'user-reinvited': (event) => this.userReinvited(event),
'user-updated': (event) => this.userUpdated(event),
'user-signed-up': (event) => this.userSignedUp(event),
'user-logged-in': (event) => this.userLoggedIn(event),
'user-login-failed': (event) => this.userLoginFailed(event),
'user-invite-email-click': (event) => this.userInviteEmailClick(event),
'user-password-reset-email-click': (event) => this.userPasswordResetEmailClick(event),
'user-password-reset-request-click': (event) => this.userPasswordResetRequestClick(event),
'public-api-key-created': (event) => this.publicApiKeyCreated(event),
'public-api-key-deleted': (event) => this.publicApiKeyDeleted(event),
'email-failed': (event) => this.emailFailed(event),
'credentials-created': (event) => this.credentialsCreated(event),
'credentials-deleted': (event) => this.credentialsDeleted(event),
'credentials-shared': (event) => this.credentialsShared(event),
'credentials-updated': (event) => this.credentialsUpdated(event),
'community-package-installed': (event) => this.communityPackageInstalled(event),
'community-package-updated': (event) => this.communityPackageUpdated(event),
'community-package-deleted': (event) => this.communityPackageDeleted(event),
'execution-throttled': (event) => this.executionThrottled(event),
'execution-started-during-bootup': (event) => this.executionStartedDuringBootup(event),
});
} }
private setupHandlers() { // #region Workflow
this.eventService.on('workflow-created', (event) => this.workflowCreated(event));
this.eventService.on('workflow-deleted', (event) => this.workflowDeleted(event));
this.eventService.on('workflow-saved', (event) => this.workflowSaved(event));
this.eventService.on('workflow-pre-execute', (event) => this.workflowPreExecute(event));
this.eventService.on('workflow-post-execute', (event) => this.workflowPostExecute(event));
this.eventService.on('node-pre-execute', (event) => this.nodePreExecute(event));
this.eventService.on('node-post-execute', (event) => this.nodePostExecute(event));
this.eventService.on('user-deleted', (event) => this.userDeleted(event));
this.eventService.on('user-invited', (event) => this.userInvited(event));
this.eventService.on('user-reinvited', (event) => this.userReinvited(event));
this.eventService.on('user-updated', (event) => this.userUpdated(event));
this.eventService.on('user-signed-up', (event) => this.userSignedUp(event));
this.eventService.on('user-logged-in', (event) => this.userLoggedIn(event));
this.eventService.on('user-login-failed', (event) => this.userLoginFailed(event));
this.eventService.on('user-invite-email-click', (event) => this.userInviteEmailClick(event));
this.eventService.on('user-password-reset-email-click', (event) =>
this.userPasswordResetEmailClick(event),
);
this.eventService.on('user-password-reset-request-click', (event) =>
this.userPasswordResetRequestClick(event),
);
this.eventService.on('public-api-key-created', (event) => this.publicApiKeyCreated(event));
this.eventService.on('public-api-key-deleted', (event) => this.publicApiKeyDeleted(event));
this.eventService.on('email-failed', (event) => this.emailFailed(event));
this.eventService.on('credentials-created', (event) => this.credentialsCreated(event));
this.eventService.on('credentials-deleted', (event) => this.credentialsDeleted(event));
this.eventService.on('credentials-shared', (event) => this.credentialsShared(event));
this.eventService.on('credentials-updated', (event) => this.credentialsUpdated(event));
this.eventService.on('credentials-deleted', (event) => this.credentialsDeleted(event));
this.eventService.on('community-package-installed', (event) =>
this.communityPackageInstalled(event),
);
this.eventService.on('community-package-updated', (event) =>
this.communityPackageUpdated(event),
);
this.eventService.on('community-package-deleted', (event) =>
this.communityPackageDeleted(event),
);
this.eventService.on('execution-throttled', (event) => this.executionThrottled(event));
this.eventService.on('execution-started-during-bootup', (event) =>
this.executionStartedDuringBootup(event),
);
}
/**
* Workflow
*/
@Redactable() @Redactable()
private workflowCreated({ user, workflow }: Event['workflow-created']) { private workflowCreated({ user, workflow }: RelayEventMap['workflow-created']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.workflow.created', eventName: 'n8n.audit.workflow.created',
payload: { payload: {
@ -78,7 +64,7 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private workflowDeleted({ user, workflowId }: Event['workflow-deleted']) { private workflowDeleted({ user, workflowId }: RelayEventMap['workflow-deleted']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.workflow.deleted', eventName: 'n8n.audit.workflow.deleted',
payload: { ...user, workflowId }, payload: { ...user, workflowId },
@ -86,7 +72,7 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private workflowSaved({ user, workflow }: Event['workflow-saved']) { private workflowSaved({ user, workflow }: RelayEventMap['workflow-saved']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.workflow.updated', eventName: 'n8n.audit.workflow.updated',
payload: { payload: {
@ -97,7 +83,7 @@ export class AuditEventRelay {
}); });
} }
private workflowPreExecute({ data, executionId }: Event['workflow-pre-execute']) { private workflowPreExecute({ data, executionId }: RelayEventMap['workflow-pre-execute']) {
const payload = const payload =
'executionData' in data 'executionData' in data
? { ? {
@ -121,7 +107,7 @@ export class AuditEventRelay {
}); });
} }
private workflowPostExecute(event: Event['workflow-post-execute']) { private workflowPostExecute(event: RelayEventMap['workflow-post-execute']) {
const { runData, workflow, ...rest } = event; const { runData, workflow, ...rest } = event;
const payload = { const payload = {
@ -155,11 +141,11 @@ export class AuditEventRelay {
}); });
} }
/** // #endregion
* Node
*/
private nodePreExecute({ workflow, executionId, nodeName }: Event['node-pre-execute']) { // #region Node
private nodePreExecute({ workflow, executionId, nodeName }: RelayEventMap['node-pre-execute']) {
void this.eventBus.sendNodeEvent({ void this.eventBus.sendNodeEvent({
eventName: 'n8n.node.started', eventName: 'n8n.node.started',
payload: { payload: {
@ -172,7 +158,7 @@ export class AuditEventRelay {
}); });
} }
private nodePostExecute({ workflow, executionId, nodeName }: Event['node-post-execute']) { private nodePostExecute({ workflow, executionId, nodeName }: RelayEventMap['node-post-execute']) {
void this.eventBus.sendNodeEvent({ void this.eventBus.sendNodeEvent({
eventName: 'n8n.node.finished', eventName: 'n8n.node.finished',
payload: { payload: {
@ -185,12 +171,12 @@ export class AuditEventRelay {
}); });
} }
/** // #endregion
* User
*/ // #region User
@Redactable() @Redactable()
private userDeleted({ user }: Event['user-deleted']) { private userDeleted({ user }: RelayEventMap['user-deleted']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.deleted', eventName: 'n8n.audit.user.deleted',
payload: user, payload: user,
@ -198,7 +184,7 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private userInvited({ user, targetUserId }: Event['user-invited']) { private userInvited({ user, targetUserId }: RelayEventMap['user-invited']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.invited', eventName: 'n8n.audit.user.invited',
payload: { ...user, targetUserId }, payload: { ...user, targetUserId },
@ -206,7 +192,7 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private userReinvited({ user, targetUserId }: Event['user-reinvited']) { private userReinvited({ user, targetUserId }: RelayEventMap['user-reinvited']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.reinvited', eventName: 'n8n.audit.user.reinvited',
payload: { ...user, targetUserId }, payload: { ...user, targetUserId },
@ -214,19 +200,19 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private userUpdated({ user, fieldsChanged }: Event['user-updated']) { private userUpdated({ user, fieldsChanged }: RelayEventMap['user-updated']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.updated', eventName: 'n8n.audit.user.updated',
payload: { ...user, fieldsChanged }, payload: { ...user, fieldsChanged },
}); });
} }
/** // #endregion
* Auth
*/ // #region Auth
@Redactable() @Redactable()
private userSignedUp({ user }: Event['user-signed-up']) { private userSignedUp({ user }: RelayEventMap['user-signed-up']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.signedup', eventName: 'n8n.audit.user.signedup',
payload: user, payload: user,
@ -234,7 +220,7 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private userLoggedIn({ user, authenticationMethod }: Event['user-logged-in']) { private userLoggedIn({ user, authenticationMethod }: RelayEventMap['user-logged-in']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.login.success', eventName: 'n8n.audit.user.login.success',
payload: { ...user, authenticationMethod }, payload: { ...user, authenticationMethod },
@ -242,7 +228,7 @@ export class AuditEventRelay {
} }
private userLoginFailed( private userLoginFailed(
event: Event['user-login-failed'] /* exception: no `UserLike` to redact */, event: RelayEventMap['user-login-failed'] /* exception: no `UserLike` to redact */,
) { ) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.login.failed', eventName: 'n8n.audit.user.login.failed',
@ -250,13 +236,13 @@ export class AuditEventRelay {
}); });
} }
/** // #endregion
* Click
*/ // #region Click
@Redactable('inviter') @Redactable('inviter')
@Redactable('invitee') @Redactable('invitee')
private userInviteEmailClick(event: Event['user-invite-email-click']) { private userInviteEmailClick(event: RelayEventMap['user-invite-email-click']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.invitation.accepted', eventName: 'n8n.audit.user.invitation.accepted',
payload: event, payload: event,
@ -264,7 +250,7 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private userPasswordResetEmailClick({ user }: Event['user-password-reset-email-click']) { private userPasswordResetEmailClick({ user }: RelayEventMap['user-password-reset-email-click']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.reset', eventName: 'n8n.audit.user.reset',
payload: user, payload: user,
@ -272,19 +258,21 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private userPasswordResetRequestClick({ user }: Event['user-password-reset-request-click']) { private userPasswordResetRequestClick({
user,
}: RelayEventMap['user-password-reset-request-click']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.reset.requested', eventName: 'n8n.audit.user.reset.requested',
payload: user, payload: user,
}); });
} }
/** // #endregion
* Public API
*/ // #region Public API
@Redactable() @Redactable()
private publicApiKeyCreated({ user }: Event['public-api-key-created']) { private publicApiKeyCreated({ user }: RelayEventMap['public-api-key-created']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.api.created', eventName: 'n8n.audit.user.api.created',
payload: user, payload: user,
@ -292,31 +280,31 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private publicApiKeyDeleted({ user }: Event['public-api-key-deleted']) { private publicApiKeyDeleted({ user }: RelayEventMap['public-api-key-deleted']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.api.deleted', eventName: 'n8n.audit.user.api.deleted',
payload: user, payload: user,
}); });
} }
/** // #endregion
* Emailing
*/ // #region Email
@Redactable() @Redactable()
private emailFailed({ user, messageType }: Event['email-failed']) { private emailFailed({ user, messageType }: RelayEventMap['email-failed']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.email.failed', eventName: 'n8n.audit.user.email.failed',
payload: { ...user, messageType }, payload: { ...user, messageType },
}); });
} }
/** // #endregion
* Credentials
*/ // #region Credentials
@Redactable() @Redactable()
private credentialsCreated({ user, ...rest }: Event['credentials-created']) { private credentialsCreated({ user, ...rest }: RelayEventMap['credentials-created']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.credentials.created', eventName: 'n8n.audit.user.credentials.created',
payload: { ...user, ...rest }, payload: { ...user, ...rest },
@ -324,7 +312,7 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private credentialsDeleted({ user, ...rest }: Event['credentials-deleted']) { private credentialsDeleted({ user, ...rest }: RelayEventMap['credentials-deleted']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.credentials.deleted', eventName: 'n8n.audit.user.credentials.deleted',
payload: { ...user, ...rest }, payload: { ...user, ...rest },
@ -332,7 +320,7 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private credentialsShared({ user, ...rest }: Event['credentials-shared']) { private credentialsShared({ user, ...rest }: RelayEventMap['credentials-shared']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.credentials.shared', eventName: 'n8n.audit.user.credentials.shared',
payload: { ...user, ...rest }, payload: { ...user, ...rest },
@ -340,19 +328,22 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private credentialsUpdated({ user, ...rest }: Event['credentials-updated']) { private credentialsUpdated({ user, ...rest }: RelayEventMap['credentials-updated']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.credentials.updated', eventName: 'n8n.audit.user.credentials.updated',
payload: { ...user, ...rest }, payload: { ...user, ...rest },
}); });
} }
/** // #endregion
* Community package
*/ // #region Community package
@Redactable() @Redactable()
private communityPackageInstalled({ user, ...rest }: Event['community-package-installed']) { private communityPackageInstalled({
user,
...rest
}: RelayEventMap['community-package-installed']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.package.installed', eventName: 'n8n.audit.package.installed',
payload: { ...user, ...rest }, payload: { ...user, ...rest },
@ -360,7 +351,7 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private communityPackageUpdated({ user, ...rest }: Event['community-package-updated']) { private communityPackageUpdated({ user, ...rest }: RelayEventMap['community-package-updated']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.package.updated', eventName: 'n8n.audit.package.updated',
payload: { ...user, ...rest }, payload: { ...user, ...rest },
@ -368,28 +359,32 @@ export class AuditEventRelay {
} }
@Redactable() @Redactable()
private communityPackageDeleted({ user, ...rest }: Event['community-package-deleted']) { private communityPackageDeleted({ user, ...rest }: RelayEventMap['community-package-deleted']) {
void this.eventBus.sendAuditEvent({ void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.package.deleted', eventName: 'n8n.audit.package.deleted',
payload: { ...user, ...rest }, payload: { ...user, ...rest },
}); });
} }
/** // #endregion
* Execution
*/
private executionThrottled({ executionId }: Event['execution-throttled']) { // #region Execution
private executionThrottled({ executionId }: RelayEventMap['execution-throttled']) {
void this.eventBus.sendExecutionEvent({ void this.eventBus.sendExecutionEvent({
eventName: 'n8n.execution.throttled', eventName: 'n8n.execution.throttled',
payload: { executionId }, payload: { executionId },
}); });
} }
private executionStartedDuringBootup({ executionId }: Event['execution-started-during-bootup']) { private executionStartedDuringBootup({
executionId,
}: RelayEventMap['execution-started-during-bootup']) {
void this.eventBus.sendExecutionEvent({ void this.eventBus.sendExecutionEvent({
eventName: 'n8n.execution.started-during-bootup', eventName: 'n8n.execution.started-during-bootup',
payload: { executionId }, payload: { executionId },
}); });
} }
// #endregion
} }

View file

@ -11,12 +11,15 @@ export type UserLike = {
role: string; role: string;
}; };
/** export type RelayEventMap = {
* Events sent by `EventService` and forwarded by relays, e.g. `AuditEventRelay` and `TelemetryEventRelay`. // #region Server
*/
export type Event = {
'server-started': {}; 'server-started': {};
// #endregion
// #region Workflow
'workflow-created': { 'workflow-created': {
user: UserLike; user: UserLike;
workflow: IWorkflowBase; workflow: IWorkflowBase;
@ -49,6 +52,10 @@ export type Event = {
runData?: IRun; runData?: IRun;
}; };
// #endregion
// #region Node
'node-pre-execute': { 'node-pre-execute': {
executionId: string; executionId: string;
workflow: IWorkflowBase; workflow: IWorkflowBase;
@ -61,6 +68,10 @@ export type Event = {
nodeName: string; nodeName: string;
}; };
// #endregion
// #region User
'user-deleted': { 'user-deleted': {
user: UserLike; user: UserLike;
}; };
@ -95,6 +106,10 @@ export type Event = {
reason?: string; reason?: string;
}; };
// #endregion
// #region Click
'user-invite-email-click': { 'user-invite-email-click': {
inviter: UserLike; inviter: UserLike;
invitee: UserLike; invitee: UserLike;
@ -108,6 +123,20 @@ export type Event = {
user: UserLike; user: UserLike;
}; };
// #endregion
// #region Public API
'public-api-key-created': {
user: UserLike;
publicApi: boolean;
};
'public-api-key-deleted': {
user: UserLike;
publicApi: boolean;
};
'public-api-invoked': { 'public-api-invoked': {
userId: string; userId: string;
path: string; path: string;
@ -115,6 +144,10 @@ export type Event = {
apiVersion: string; apiVersion: string;
}; };
// #endregion
// #region Email
'email-failed': { 'email-failed': {
user: UserLike; user: UserLike;
messageType: messageType:
@ -125,6 +158,10 @@ export type Event = {
| 'Credentials shared'; | 'Credentials shared';
}; };
// #endregion
// #region Credentials
'credentials-created': { 'credentials-created': {
user: UserLike; user: UserLike;
credentialType: string; credentialType: string;
@ -155,6 +192,10 @@ export type Event = {
credentialId: string; credentialId: string;
}; };
// #endregion
// #region Community package
'community-package-installed': { 'community-package-installed': {
user: UserLike; user: UserLike;
inputString: string; inputString: string;
@ -186,6 +227,10 @@ export type Event = {
packageAuthorEmail?: string; packageAuthorEmail?: string;
}; };
// #endregion
// #region Execution
'execution-throttled': { 'execution-throttled': {
executionId: string; executionId: string;
}; };
@ -194,6 +239,10 @@ export type Event = {
executionId: string; executionId: string;
}; };
// #endregion
// #region Project
'team-project-updated': { 'team-project-updated': {
userId: string; userId: string;
role: GlobalRole; role: GlobalRole;
@ -217,6 +266,10 @@ export type Event = {
role: GlobalRole; role: GlobalRole;
}; };
// #endregion
// #region Source control
'source-control-settings-updated': { 'source-control-settings-updated': {
branchName: string; branchName: string;
readOnlyInstance: boolean; readOnlyInstance: boolean;
@ -254,12 +307,24 @@ export type Event = {
variablesPushed: number; variablesPushed: number;
}; };
// #endregion
// #region License
'license-renewal-attempted': { 'license-renewal-attempted': {
success: boolean; success: boolean;
}; };
// #endregion
// #region Variable
'variable-created': {}; 'variable-created': {};
// #endregion
// #region External secrets
'external-secrets-provider-settings-saved': { 'external-secrets-provider-settings-saved': {
userId?: string; userId?: string;
vaultType: string; vaultType: string;
@ -268,6 +333,10 @@ export type Event = {
errorMessage?: string; errorMessage?: string;
}; };
// #endregion
// #region LDAP
'ldap-general-sync-finished': { 'ldap-general-sync-finished': {
type: string; type: string;
succeeded: boolean; succeeded: boolean;
@ -298,17 +367,5 @@ export type Event = {
userId: string; userId: string;
}; };
/** // #endregion
* Events listened to by more than one relay
*/
'public-api-key-created': {
user: UserLike; // audit and telemetry
publicApi: boolean; // telemetry only
};
'public-api-key-deleted': {
user: UserLike; // audit and telemetry
publicApi: boolean; // telemetry only
};
}; };

View file

@ -1,7 +1,7 @@
import { Service } from 'typedi'; import { Service } from 'typedi';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
import type { Event } from '@/eventbus/event.types'; import type { RelayEventMap } from '@/events/relay-event-map';
import { Telemetry } from '.'; import { Telemetry } from '../telemetry';
import config from '@/config'; import config from '@/config';
import os from 'node:os'; import os from 'node:os';
import { License } from '@/License'; import { License } from '@/License';
@ -16,11 +16,12 @@ import { SharedWorkflowRepository } from '@/databases/repositories/sharedWorkflo
import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository'; import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository';
import type { IExecutionTrackProperties } from '@/Interfaces'; import type { IExecutionTrackProperties } from '@/Interfaces';
import { determineFinalExecutionStatus } from '@/executionLifecycleHooks/shared/sharedHookFunctions'; import { determineFinalExecutionStatus } from '@/executionLifecycleHooks/shared/sharedHookFunctions';
import { EventRelay } from './event-relay';
@Service() @Service()
export class TelemetryEventRelay { export class TelemetryEventRelay extends EventRelay {
constructor( constructor(
private readonly eventService: EventService, readonly eventService: EventService,
private readonly telemetry: Telemetry, private readonly telemetry: Telemetry,
private readonly license: License, private readonly license: License,
private readonly globalConfig: GlobalConfig, private readonly globalConfig: GlobalConfig,
@ -28,106 +29,63 @@ export class TelemetryEventRelay {
private readonly nodeTypes: NodeTypes, private readonly nodeTypes: NodeTypes,
private readonly sharedWorkflowRepository: SharedWorkflowRepository, private readonly sharedWorkflowRepository: SharedWorkflowRepository,
private readonly projectRelationRepository: ProjectRelationRepository, private readonly projectRelationRepository: ProjectRelationRepository,
) {} ) {
super(eventService);
}
async init() { async init() {
if (!config.getEnv('diagnostics.enabled')) return; if (!config.getEnv('diagnostics.enabled')) return;
await this.telemetry.init(); await this.telemetry.init();
this.setupHandlers(); this.setupListeners({
} 'team-project-updated': (event) => this.teamProjectUpdated(event),
'team-project-deleted': (event) => this.teamProjectDeleted(event),
private setupHandlers() { 'team-project-created': (event) => this.teamProjectCreated(event),
this.eventService.on('server-started', async () => await this.serverStarted()); 'source-control-settings-updated': (event) => this.sourceControlSettingsUpdated(event),
'source-control-user-started-pull-ui': (event) => this.sourceControlUserStartedPullUi(event),
this.eventService.on('team-project-updated', (event) => this.teamProjectUpdated(event)); 'source-control-user-finished-pull-ui': (event) =>
this.eventService.on('team-project-deleted', (event) => this.teamProjectDeleted(event)); this.sourceControlUserFinishedPullUi(event),
this.eventService.on('team-project-created', (event) => this.teamProjectCreated(event)); 'source-control-user-pulled-api': (event) => this.sourceControlUserPulledApi(event),
this.eventService.on('source-control-settings-updated', (event) => 'source-control-user-started-push-ui': (event) => this.sourceControlUserStartedPushUi(event),
this.sourceControlSettingsUpdated(event), 'source-control-user-finished-push-ui': (event) =>
); this.sourceControlUserFinishedPushUi(event),
this.eventService.on('source-control-user-started-pull-ui', (event) => 'license-renewal-attempted': (event) => this.licenseRenewalAttempted(event),
this.sourceControlUserStartedPullUi(event), 'variable-created': () => this.variableCreated(),
); 'external-secrets-provider-settings-saved': (event) =>
this.eventService.on('source-control-user-finished-pull-ui', (event) => this.externalSecretsProviderSettingsSaved(event),
this.sourceControlUserFinishedPullUi(event), 'public-api-invoked': (event) => this.publicApiInvoked(event),
); 'public-api-key-created': (event) => this.publicApiKeyCreated(event),
this.eventService.on('source-control-user-pulled-api', (event) => 'public-api-key-deleted': (event) => this.publicApiKeyDeleted(event),
this.sourceControlUserPulledApi(event), 'community-package-installed': (event) => this.communityPackageInstalled(event),
); 'community-package-updated': (event) => this.communityPackageUpdated(event),
this.eventService.on('source-control-user-started-push-ui', (event) => 'community-package-deleted': (event) => this.communityPackageDeleted(event),
this.sourceControlUserStartedPushUi(event), 'credentials-created': (event) => this.credentialsCreated(event),
); 'credentials-shared': (event) => this.credentialsShared(event),
this.eventService.on('source-control-user-finished-push-ui', (event) => 'credentials-updated': (event) => this.credentialsUpdated(event),
this.sourceControlUserFinishedPushUi(event), 'credentials-deleted': (event) => this.credentialsDeleted(event),
); 'ldap-general-sync-finished': (event) => this.ldapGeneralSyncFinished(event),
this.eventService.on('license-renewal-attempted', (event) => { 'ldap-settings-updated': (event) => this.ldapSettingsUpdated(event),
this.licenseRenewalAttempted(event); 'ldap-login-sync-failed': (event) => this.ldapLoginSyncFailed(event),
}); 'login-failed-due-to-ldap-disabled': (event) => this.loginFailedDueToLdapDisabled(event),
this.eventService.on('variable-created', () => this.variableCreated()); 'workflow-created': (event) => this.workflowCreated(event),
this.eventService.on('external-secrets-provider-settings-saved', (event) => { 'workflow-deleted': (event) => this.workflowDeleted(event),
this.externalSecretsProviderSettingsSaved(event); 'workflow-saved': async (event) => await this.workflowSaved(event),
}); 'server-started': async () => await this.serverStarted(),
this.eventService.on('public-api-invoked', (event) => { 'workflow-post-execute': async (event) => await this.workflowPostExecute(event),
this.publicApiInvoked(event);
});
this.eventService.on('public-api-key-created', (event) => {
this.publicApiKeyCreated(event);
});
this.eventService.on('public-api-key-deleted', (event) => {
this.publicApiKeyDeleted(event);
});
this.eventService.on('community-package-installed', (event) => {
this.communityPackageInstalled(event);
});
this.eventService.on('community-package-updated', (event) => {
this.communityPackageUpdated(event);
});
this.eventService.on('community-package-deleted', (event) => {
this.communityPackageDeleted(event);
});
this.eventService.on('credentials-created', (event) => {
this.credentialsCreated(event);
});
this.eventService.on('credentials-shared', (event) => {
this.credentialsShared(event);
});
this.eventService.on('credentials-updated', (event) => {
this.credentialsUpdated(event);
});
this.eventService.on('credentials-deleted', (event) => {
this.credentialsDeleted(event);
});
this.eventService.on('ldap-general-sync-finished', (event) => {
this.ldapGeneralSyncFinished(event);
});
this.eventService.on('ldap-settings-updated', (event) => {
this.ldapSettingsUpdated(event);
});
this.eventService.on('ldap-login-sync-failed', (event) => {
this.ldapLoginSyncFailed(event);
});
this.eventService.on('login-failed-due-to-ldap-disabled', (event) => {
this.loginFailedDueToLdapDisabled(event);
});
this.eventService.on('workflow-created', (event) => {
this.workflowCreated(event);
});
this.eventService.on('workflow-deleted', (event) => {
this.workflowDeleted(event);
});
this.eventService.on('workflow-saved', async (event) => {
await this.workflowSaved(event);
});
this.eventService.on('workflow-post-execute', async (event) => {
await this.workflowPostExecute(event);
}); });
} }
private teamProjectUpdated({ userId, role, members, projectId }: Event['team-project-updated']) { // #endregion
// #region Team
private teamProjectUpdated({
userId,
role,
members,
projectId,
}: RelayEventMap['team-project-updated']) {
this.telemetry.track('Project settings updated', { this.telemetry.track('Project settings updated', {
user_id: userId, user_id: userId,
role, role,
@ -143,7 +101,7 @@ export class TelemetryEventRelay {
projectId, projectId,
removalType, removalType,
targetProjectId, targetProjectId,
}: Event['team-project-deleted']) { }: RelayEventMap['team-project-deleted']) {
this.telemetry.track('User deleted project', { this.telemetry.track('User deleted project', {
user_id: userId, user_id: userId,
role, role,
@ -153,19 +111,23 @@ export class TelemetryEventRelay {
}); });
} }
private teamProjectCreated({ userId, role }: Event['team-project-created']) { private teamProjectCreated({ userId, role }: RelayEventMap['team-project-created']) {
this.telemetry.track('User created project', { this.telemetry.track('User created project', {
user_id: userId, user_id: userId,
role, role,
}); });
} }
// #endregion
// #region Source control
private sourceControlSettingsUpdated({ private sourceControlSettingsUpdated({
branchName, branchName,
readOnlyInstance, readOnlyInstance,
repoType, repoType,
connected, connected,
}: Event['source-control-settings-updated']) { }: RelayEventMap['source-control-settings-updated']) {
this.telemetry.track('User updated source control settings', { this.telemetry.track('User updated source control settings', {
branch_name: branchName, branch_name: branchName,
read_only_instance: readOnlyInstance, read_only_instance: readOnlyInstance,
@ -178,7 +140,7 @@ export class TelemetryEventRelay {
workflowUpdates, workflowUpdates,
workflowConflicts, workflowConflicts,
credConflicts, credConflicts,
}: Event['source-control-user-started-pull-ui']) { }: RelayEventMap['source-control-user-started-pull-ui']) {
this.telemetry.track('User started pull via UI', { this.telemetry.track('User started pull via UI', {
workflow_updates: workflowUpdates, workflow_updates: workflowUpdates,
workflow_conflicts: workflowConflicts, workflow_conflicts: workflowConflicts,
@ -188,7 +150,7 @@ export class TelemetryEventRelay {
private sourceControlUserFinishedPullUi({ private sourceControlUserFinishedPullUi({
workflowUpdates, workflowUpdates,
}: Event['source-control-user-finished-pull-ui']) { }: RelayEventMap['source-control-user-finished-pull-ui']) {
this.telemetry.track('User finished pull via UI', { this.telemetry.track('User finished pull via UI', {
workflow_updates: workflowUpdates, workflow_updates: workflowUpdates,
}); });
@ -197,7 +159,7 @@ export class TelemetryEventRelay {
private sourceControlUserPulledApi({ private sourceControlUserPulledApi({
workflowUpdates, workflowUpdates,
forced, forced,
}: Event['source-control-user-pulled-api']) { }: RelayEventMap['source-control-user-pulled-api']) {
console.log('source-control-user-pulled-api', { console.log('source-control-user-pulled-api', {
workflow_updates: workflowUpdates, workflow_updates: workflowUpdates,
forced, forced,
@ -214,7 +176,7 @@ export class TelemetryEventRelay {
credsEligible, credsEligible,
credsEligibleWithConflicts, credsEligibleWithConflicts,
variablesEligible, variablesEligible,
}: Event['source-control-user-started-push-ui']) { }: RelayEventMap['source-control-user-started-push-ui']) {
this.telemetry.track('User started push via UI', { this.telemetry.track('User started push via UI', {
workflows_eligible: workflowsEligible, workflows_eligible: workflowsEligible,
workflows_eligible_with_conflicts: workflowsEligibleWithConflicts, workflows_eligible_with_conflicts: workflowsEligibleWithConflicts,
@ -229,7 +191,7 @@ export class TelemetryEventRelay {
workflowsPushed, workflowsPushed,
credsPushed, credsPushed,
variablesPushed, variablesPushed,
}: Event['source-control-user-finished-push-ui']) { }: RelayEventMap['source-control-user-finished-push-ui']) {
this.telemetry.track('User finished push via UI', { this.telemetry.track('User finished push via UI', {
workflows_eligible: workflowsEligible, workflows_eligible: workflowsEligible,
workflows_pushed: workflowsPushed, workflows_pushed: workflowsPushed,
@ -238,23 +200,35 @@ export class TelemetryEventRelay {
}); });
} }
private licenseRenewalAttempted({ success }: Event['license-renewal-attempted']) { // #endregion
// #region License
private licenseRenewalAttempted({ success }: RelayEventMap['license-renewal-attempted']) {
this.telemetry.track('Instance attempted to refresh license', { this.telemetry.track('Instance attempted to refresh license', {
success, success,
}); });
} }
// #endregion
// #region Variable
private variableCreated() { private variableCreated() {
this.telemetry.track('User created variable'); this.telemetry.track('User created variable');
} }
// #endregion
// #region External secrets
private externalSecretsProviderSettingsSaved({ private externalSecretsProviderSettingsSaved({
userId, userId,
vaultType, vaultType,
isValid, isValid,
isNew, isNew,
errorMessage, errorMessage,
}: Event['external-secrets-provider-settings-saved']) { }: RelayEventMap['external-secrets-provider-settings-saved']) {
this.telemetry.track('User updated external secrets settings', { this.telemetry.track('User updated external secrets settings', {
user_id: userId, user_id: userId,
vault_type: vaultType, vault_type: vaultType,
@ -264,7 +238,16 @@ export class TelemetryEventRelay {
}); });
} }
private publicApiInvoked({ userId, path, method, apiVersion }: Event['public-api-invoked']) { // #endregion
// #region Public API
private publicApiInvoked({
userId,
path,
method,
apiVersion,
}: RelayEventMap['public-api-invoked']) {
this.telemetry.track('User invoked API', { this.telemetry.track('User invoked API', {
user_id: userId, user_id: userId,
path, path,
@ -273,7 +256,7 @@ export class TelemetryEventRelay {
}); });
} }
private publicApiKeyCreated(event: Event['public-api-key-created']) { private publicApiKeyCreated(event: RelayEventMap['public-api-key-created']) {
const { user, publicApi } = event; const { user, publicApi } = event;
this.telemetry.track('API key created', { this.telemetry.track('API key created', {
@ -282,7 +265,7 @@ export class TelemetryEventRelay {
}); });
} }
private publicApiKeyDeleted(event: Event['public-api-key-deleted']) { private publicApiKeyDeleted(event: RelayEventMap['public-api-key-deleted']) {
const { user, publicApi } = event; const { user, publicApi } = event;
this.telemetry.track('API key deleted', { this.telemetry.track('API key deleted', {
@ -291,6 +274,10 @@ export class TelemetryEventRelay {
}); });
} }
// #endregion
// #region Community package
private communityPackageInstalled({ private communityPackageInstalled({
user, user,
inputString, inputString,
@ -301,7 +288,7 @@ export class TelemetryEventRelay {
packageAuthor, packageAuthor,
packageAuthorEmail, packageAuthorEmail,
failureReason, failureReason,
}: Event['community-package-installed']) { }: RelayEventMap['community-package-installed']) {
this.telemetry.track('cnr package install finished', { this.telemetry.track('cnr package install finished', {
user_id: user.id, user_id: user.id,
input_string: inputString, input_string: inputString,
@ -323,7 +310,7 @@ export class TelemetryEventRelay {
packageNodeNames, packageNodeNames,
packageAuthor, packageAuthor,
packageAuthorEmail, packageAuthorEmail,
}: Event['community-package-updated']) { }: RelayEventMap['community-package-updated']) {
this.telemetry.track('cnr package updated', { this.telemetry.track('cnr package updated', {
user_id: user.id, user_id: user.id,
package_name: packageName, package_name: packageName,
@ -342,7 +329,7 @@ export class TelemetryEventRelay {
packageNodeNames, packageNodeNames,
packageAuthor, packageAuthor,
packageAuthorEmail, packageAuthorEmail,
}: Event['community-package-deleted']) { }: RelayEventMap['community-package-deleted']) {
this.telemetry.track('cnr package deleted', { this.telemetry.track('cnr package deleted', {
user_id: user.id, user_id: user.id,
package_name: packageName, package_name: packageName,
@ -353,13 +340,17 @@ export class TelemetryEventRelay {
}); });
} }
// #endregion
// #region Credentials
private credentialsCreated({ private credentialsCreated({
user, user,
credentialType, credentialType,
credentialId, credentialId,
projectId, projectId,
projectType, projectType,
}: Event['credentials-created']) { }: RelayEventMap['credentials-created']) {
this.telemetry.track('User created credentials', { this.telemetry.track('User created credentials', {
user_id: user.id, user_id: user.id,
credential_type: credentialType, credential_type: credentialType,
@ -376,7 +367,7 @@ export class TelemetryEventRelay {
userIdSharer, userIdSharer,
userIdsShareesAdded, userIdsShareesAdded,
shareesRemoved, shareesRemoved,
}: Event['credentials-shared']) { }: RelayEventMap['credentials-shared']) {
this.telemetry.track('User updated cred sharing', { this.telemetry.track('User updated cred sharing', {
user_id: user.id, user_id: user.id,
credential_type: credentialType, credential_type: credentialType,
@ -387,7 +378,11 @@ export class TelemetryEventRelay {
}); });
} }
private credentialsUpdated({ user, credentialId, credentialType }: Event['credentials-updated']) { private credentialsUpdated({
user,
credentialId,
credentialType,
}: RelayEventMap['credentials-updated']) {
this.telemetry.track('User updated credentials', { this.telemetry.track('User updated credentials', {
user_id: user.id, user_id: user.id,
credential_type: credentialType, credential_type: credentialType,
@ -395,7 +390,11 @@ export class TelemetryEventRelay {
}); });
} }
private credentialsDeleted({ user, credentialId, credentialType }: Event['credentials-deleted']) { private credentialsDeleted({
user,
credentialId,
credentialType,
}: RelayEventMap['credentials-deleted']) {
this.telemetry.track('User deleted credentials', { this.telemetry.track('User deleted credentials', {
user_id: user.id, user_id: user.id,
credential_type: credentialType, credential_type: credentialType,
@ -403,12 +402,16 @@ export class TelemetryEventRelay {
}); });
} }
// #endregion
// #region LDAP
private ldapGeneralSyncFinished({ private ldapGeneralSyncFinished({
type, type,
succeeded, succeeded,
usersSynced, usersSynced,
error, error,
}: Event['ldap-general-sync-finished']) { }: RelayEventMap['ldap-general-sync-finished']) {
this.telemetry.track('Ldap general sync finished', { this.telemetry.track('Ldap general sync finished', {
type, type,
succeeded, succeeded,
@ -430,7 +433,7 @@ export class TelemetryEventRelay {
synchronizationInterval, synchronizationInterval,
loginLabel, loginLabel,
loginEnabled, loginEnabled,
}: Event['ldap-settings-updated']) { }: RelayEventMap['ldap-settings-updated']) {
this.telemetry.track('User updated Ldap settings', { this.telemetry.track('User updated Ldap settings', {
user_id: userId, user_id: userId,
loginIdAttribute, loginIdAttribute,
@ -447,21 +450,27 @@ export class TelemetryEventRelay {
}); });
} }
private ldapLoginSyncFailed({ error }: Event['ldap-login-sync-failed']) { private ldapLoginSyncFailed({ error }: RelayEventMap['ldap-login-sync-failed']) {
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']) { private loginFailedDueToLdapDisabled({
userId,
}: RelayEventMap['login-failed-due-to-ldap-disabled']) {
this.telemetry.track('User login failed since ldap disabled', { user_ud: userId }); this.telemetry.track('User login failed since ldap disabled', { user_ud: userId });
} }
// #endregion
// #region Workflow
private workflowCreated({ private workflowCreated({
user, user,
workflow, workflow,
publicApi, publicApi,
projectId, projectId,
projectType, projectType,
}: Event['workflow-created']) { }: RelayEventMap['workflow-created']) {
const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes); const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes);
this.telemetry.track('User created workflow', { this.telemetry.track('User created workflow', {
@ -474,7 +483,7 @@ export class TelemetryEventRelay {
}); });
} }
private workflowDeleted({ user, workflowId, publicApi }: Event['workflow-deleted']) { private workflowDeleted({ user, workflowId, publicApi }: RelayEventMap['workflow-deleted']) {
this.telemetry.track('User deleted workflow', { this.telemetry.track('User deleted workflow', {
user_id: user.id, user_id: user.id,
workflow_id: workflowId, workflow_id: workflowId,
@ -482,7 +491,7 @@ export class TelemetryEventRelay {
}); });
} }
private async workflowSaved({ user, workflow, publicApi }: Event['workflow-saved']) { private async workflowSaved({ user, workflow, publicApi }: RelayEventMap['workflow-saved']) {
const isCloudDeployment = config.getEnv('deployment.type') === 'cloud'; const isCloudDeployment = config.getEnv('deployment.type') === 'cloud';
const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes, { const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes, {
@ -528,72 +537,12 @@ export class TelemetryEventRelay {
}); });
} }
private async serverStarted() {
const cpus = os.cpus();
const binaryDataConfig = config.getEnv('binaryDataManager');
const isS3Selected = config.getEnv('binaryDataManager.mode') === 's3';
const isS3Available = config.getEnv('binaryDataManager.availableModes').includes('s3');
const isS3Licensed = this.license.isBinaryDataS3Licensed();
const authenticationMethod = config.getEnv('userManagement.authenticationMethod');
const info = {
version_cli: N8N_VERSION,
db_type: this.globalConfig.database.type,
n8n_version_notifications_enabled: this.globalConfig.versionNotifications.enabled,
n8n_disable_production_main_process:
this.globalConfig.endpoints.disableProductionWebhooksOnMainProcess,
system_info: {
os: {
type: os.type(),
version: os.version(),
},
memory: os.totalmem() / 1024,
cpus: {
count: cpus.length,
model: cpus[0].model,
speed: cpus[0].speed,
},
},
execution_variables: {
executions_mode: config.getEnv('executions.mode'),
executions_timeout: config.getEnv('executions.timeout'),
executions_timeout_max: config.getEnv('executions.maxTimeout'),
executions_data_save_on_error: config.getEnv('executions.saveDataOnError'),
executions_data_save_on_success: config.getEnv('executions.saveDataOnSuccess'),
executions_data_save_on_progress: config.getEnv('executions.saveExecutionProgress'),
executions_data_save_manual_executions: config.getEnv(
'executions.saveDataManualExecutions',
),
executions_data_prune: config.getEnv('executions.pruneData'),
executions_data_max_age: config.getEnv('executions.pruneDataMaxAge'),
},
n8n_deployment_type: config.getEnv('deployment.type'),
n8n_binary_data_mode: binaryDataConfig.mode,
smtp_set_up: this.globalConfig.userManagement.emails.mode === 'smtp',
ldap_allowed: authenticationMethod === 'ldap',
saml_enabled: authenticationMethod === 'saml',
license_plan_name: this.license.getPlanName(),
license_tenant_id: config.getEnv('license.tenantId'),
binary_data_s3: isS3Available && isS3Selected && isS3Licensed,
multi_main_setup_enabled: config.getEnv('multiMainSetup.enabled'),
};
const firstWorkflow = await this.workflowRepository.findOne({
select: ['createdAt'],
order: { createdAt: 'ASC' },
where: {},
});
this.telemetry.identify(info);
this.telemetry.track('Instance started', {
...info,
earliest_workflow_created: firstWorkflow?.createdAt,
});
}
// eslint-disable-next-line complexity // eslint-disable-next-line complexity
private async workflowPostExecute({ workflow, runData, userId }: Event['workflow-post-execute']) { private async workflowPostExecute({
workflow,
runData,
userId,
}: RelayEventMap['workflow-post-execute']) {
if (!workflow.id) { if (!workflow.id) {
return; return;
} }
@ -725,4 +674,74 @@ export class TelemetryEventRelay {
this.telemetry.trackWorkflowExecution(telemetryProperties); this.telemetry.trackWorkflowExecution(telemetryProperties);
} }
// #endregion
// #region Server
private async serverStarted() {
const cpus = os.cpus();
const binaryDataConfig = config.getEnv('binaryDataManager');
const isS3Selected = config.getEnv('binaryDataManager.mode') === 's3';
const isS3Available = config.getEnv('binaryDataManager.availableModes').includes('s3');
const isS3Licensed = this.license.isBinaryDataS3Licensed();
const authenticationMethod = config.getEnv('userManagement.authenticationMethod');
const info = {
version_cli: N8N_VERSION,
db_type: this.globalConfig.database.type,
n8n_version_notifications_enabled: this.globalConfig.versionNotifications.enabled,
n8n_disable_production_main_process:
this.globalConfig.endpoints.disableProductionWebhooksOnMainProcess,
system_info: {
os: {
type: os.type(),
version: os.version(),
},
memory: os.totalmem() / 1024,
cpus: {
count: cpus.length,
model: cpus[0].model,
speed: cpus[0].speed,
},
},
execution_variables: {
executions_mode: config.getEnv('executions.mode'),
executions_timeout: config.getEnv('executions.timeout'),
executions_timeout_max: config.getEnv('executions.maxTimeout'),
executions_data_save_on_error: config.getEnv('executions.saveDataOnError'),
executions_data_save_on_success: config.getEnv('executions.saveDataOnSuccess'),
executions_data_save_on_progress: config.getEnv('executions.saveExecutionProgress'),
executions_data_save_manual_executions: config.getEnv(
'executions.saveDataManualExecutions',
),
executions_data_prune: config.getEnv('executions.pruneData'),
executions_data_max_age: config.getEnv('executions.pruneDataMaxAge'),
},
n8n_deployment_type: config.getEnv('deployment.type'),
n8n_binary_data_mode: binaryDataConfig.mode,
smtp_set_up: this.globalConfig.userManagement.emails.mode === 'smtp',
ldap_allowed: authenticationMethod === 'ldap',
saml_enabled: authenticationMethod === 'saml',
license_plan_name: this.license.getPlanName(),
license_tenant_id: config.getEnv('license.tenantId'),
binary_data_s3: isS3Available && isS3Selected && isS3Licensed,
multi_main_setup_enabled: config.getEnv('multiMainSetup.enabled'),
};
const firstWorkflow = await this.workflowRepository.findOne({
select: ['createdAt'],
order: { createdAt: 'ASC' },
where: {},
});
this.telemetry.identify(info);
this.telemetry.track('Instance started', {
...info,
earliest_workflow_created: firstWorkflow?.createdAt,
});
}
// #endregion
} }

View file

@ -16,7 +16,7 @@ import config from '@/config';
import { OnShutdown } from '@/decorators/OnShutdown'; import { OnShutdown } from '@/decorators/OnShutdown';
import type { QueueRecoverySettings } from './execution.types'; import type { QueueRecoverySettings } from './execution.types';
import { OrchestrationService } from '@/services/orchestration.service'; import { OrchestrationService } from '@/services/orchestration.service';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
/** /**
* Service for recovering key properties in executions. * Service for recovering key properties in executions.

View file

@ -3,7 +3,7 @@ import axios from 'axios';
import { Logger } from '@/Logger'; import { Logger } from '@/Logger';
import { License } from '@/License'; import { License } from '@/License';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
import type { User } from '@db/entities/User'; import type { User } from '@db/entities/User';
import { WorkflowRepository } from '@db/repositories/workflow.repository'; import { WorkflowRepository } from '@db/repositories/workflow.repository';
import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { BadRequestError } from '@/errors/response-errors/bad-request.error';

View file

@ -12,7 +12,7 @@ import { InternalHooks } from '@/InternalHooks';
import { UrlService } from '@/services/url.service'; import { UrlService } from '@/services/url.service';
import type { UserRequest } from '@/requests'; import type { UserRequest } from '@/requests';
import { InternalServerError } from '@/errors/response-errors/internal-server.error'; import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
@Service() @Service()
export class UserService { export class UserService {

View file

@ -27,7 +27,7 @@ import {
import { SamlService } from '../saml.service.ee'; import { SamlService } from '../saml.service.ee';
import { SamlConfiguration } from '../types/requests'; import { SamlConfiguration } from '../types/requests';
import { getInitSSOFormView } from '../views/initSsoPost'; import { getInitSSOFormView } from '../views/initSsoPost';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
@RestController('/sso/saml') @RestController('/sso/saml')
export class SamlController { export class SamlController {

View file

@ -33,7 +33,7 @@ import type { EntityManager } from '@n8n/typeorm';
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import // eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
import { In } from '@n8n/typeorm'; import { In } from '@n8n/typeorm';
import { SharedWorkflow } from '@/databases/entities/SharedWorkflow'; import { SharedWorkflow } from '@/databases/entities/SharedWorkflow';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
@Service() @Service()
export class WorkflowService { export class WorkflowService {

View file

@ -42,7 +42,7 @@ import { In, type FindOptionsRelations } from '@n8n/typeorm';
import type { Project } from '@/databases/entities/Project'; import type { Project } from '@/databases/entities/Project';
import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository'; import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository';
import { z } from 'zod'; import { z } from 'zod';
import { EventService } from '@/eventbus/event.service'; import { EventService } from '@/events/event.service';
import { GlobalConfig } from '@n8n/config'; import { GlobalConfig } from '@n8n/config';
@RestController('/workflows') @RestController('/workflows')

View file

@ -21,7 +21,7 @@ import {
TestFailProvider, TestFailProvider,
} from '../../shared/ExternalSecrets/utils'; } from '../../shared/ExternalSecrets/utils';
import type { SuperAgentTest } from '../shared/types'; import type { SuperAgentTest } from '../shared/types';
import type { EventService } from '@/eventbus/event.service'; import type { EventService } from '@/events/event.service';
let authOwnerAgent: SuperAgentTest; let authOwnerAgent: SuperAgentTest;
let authMemberAgent: SuperAgentTest; let authMemberAgent: SuperAgentTest;

View file

@ -15,7 +15,7 @@ import { type JobQueue, Queue } from '@/Queue';
import { setupTestCommand } from '@test-integration/utils/testCommand'; import { setupTestCommand } from '@test-integration/utils/testCommand';
import { mockInstance } from '../../shared/mocking'; import { mockInstance } from '../../shared/mocking';
import { AuditEventRelay } from '@/eventbus/audit-event-relay.service'; import { LogStreamingEventRelay } from '@/events/log-streaming-event-relay';
config.set('executions.mode', 'queue'); config.set('executions.mode', 'queue');
config.set('binaryDataManager.availableModes', 'filesystem'); config.set('binaryDataManager.availableModes', 'filesystem');
@ -26,7 +26,7 @@ const externalHooks = mockInstance(ExternalHooks);
const externalSecretsManager = mockInstance(ExternalSecretsManager); const externalSecretsManager = mockInstance(ExternalSecretsManager);
const license = mockInstance(License); const license = mockInstance(License);
const messageEventBus = mockInstance(MessageEventBus); const messageEventBus = mockInstance(MessageEventBus);
const auditEventRelay = mockInstance(AuditEventRelay); const logStreamingEventRelay = mockInstance(LogStreamingEventRelay);
const orchestrationHandlerWorkerService = mockInstance(OrchestrationHandlerWorkerService); const orchestrationHandlerWorkerService = mockInstance(OrchestrationHandlerWorkerService);
const queue = mockInstance(Queue); const queue = mockInstance(Queue);
const orchestrationWorkerService = mockInstance(OrchestrationWorkerService); const orchestrationWorkerService = mockInstance(OrchestrationWorkerService);
@ -45,7 +45,7 @@ test('worker initializes all its components', async () => {
expect(externalHooks.init).toHaveBeenCalledTimes(1); expect(externalHooks.init).toHaveBeenCalledTimes(1);
expect(externalSecretsManager.init).toHaveBeenCalledTimes(1); expect(externalSecretsManager.init).toHaveBeenCalledTimes(1);
expect(messageEventBus.initialize).toHaveBeenCalledTimes(1); expect(messageEventBus.initialize).toHaveBeenCalledTimes(1);
expect(auditEventRelay.init).toHaveBeenCalledTimes(1); expect(logStreamingEventRelay.init).toHaveBeenCalledTimes(1);
expect(queue.init).toHaveBeenCalledTimes(1); expect(queue.init).toHaveBeenCalledTimes(1);
expect(queue.process).toHaveBeenCalledTimes(1); expect(queue.process).toHaveBeenCalledTimes(1);
expect(orchestrationWorkerService.init).toHaveBeenCalledTimes(1); expect(orchestrationWorkerService.init).toHaveBeenCalledTimes(1);

View file

@ -4,7 +4,7 @@ import { mock } from 'jest-mock-extended';
import type { BaseCommand } from '@/commands/BaseCommand'; import type { BaseCommand } from '@/commands/BaseCommand';
import * as testDb from '../testDb'; import * as testDb from '../testDb';
import { TelemetryEventRelay } from '@/telemetry/telemetry-event-relay.service'; import { TelemetryEventRelay } from '@/events/telemetry-event-relay';
import { mockInstance } from '@test/mocking'; import { mockInstance } from '@test/mocking';
export const setupTestCommand = <T extends BaseCommand>(Command: Class<T>) => { export const setupTestCommand = <T extends BaseCommand>(Command: Class<T>) => {

View file

@ -1,6 +1,6 @@
import { LicenseErrors, LicenseService } from '@/license/license.service'; import { LicenseErrors, LicenseService } from '@/license/license.service';
import type { License } from '@/License'; import type { License } from '@/License';
import type { EventService } from '@/eventbus/event.service'; import type { EventService } from '@/events/event.service';
import type { WorkflowRepository } from '@db/repositories/workflow.repository'; import type { WorkflowRepository } from '@db/repositories/workflow.repository';
import type { TEntitlement } from '@n8n_io/license-sdk'; import type { TEntitlement } from '@n8n_io/license-sdk';
import { mock } from 'jest-mock-extended'; import { mock } from 'jest-mock-extended';