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 { EXTERNAL_SECRETS_INITIAL_BACKOFF, EXTERNAL_SECRETS_MAX_BACKOFF } from './constants';
import { License } from '@/License';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
import { updateIntervalTime } from './externalSecretsHelper.ee';
import { ExternalSecretsProviders } from './ExternalSecretsProviders.ee';
import { OrchestrationService } from '@/services/orchestration.service';

View file

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

View file

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

View file

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

View file

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

View file

@ -17,7 +17,7 @@ import { Container } from 'typedi';
import { CredentialsRepository } from '@db/repositories/credentials.repository';
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.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> {
return await Container.get(CredentialsRepository).findOneBy({ id: credentialId });

View file

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

View file

@ -32,7 +32,7 @@ import { SharedWorkflowRepository } from '@/databases/repositories/sharedWorkflo
import { TagRepository } from '@/databases/repositories/tag.repository';
import { WorkflowRepository } from '@/databases/repositories/workflow.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 { 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 type { FrontendService } from '@/services/frontend.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/auth.controller';
@ -64,7 +64,7 @@ import '@/ExternalSecrets/ExternalSecrets.controller.ee';
import '@/license/license.controller';
import '@/workflows/workflowHistory/workflowHistory.controller.ee';
import '@/workflows/workflows.controller';
import { EventService } from './eventbus/event.service';
import { EventService } from './events/event.service';
const exec = promisify(callbackExec);
@ -250,7 +250,7 @@ export class Server extends AbstractServer {
// ----------------------------------------
const eventBus = Container.get(MessageEventBus);
await eventBus.initialize();
Container.get(AuditEventRelay).init();
Container.get(LogStreamingEventRelay).init();
if (this.endpointPresetCredentials !== '') {
// POST endpoint to set preset credentials

View file

@ -16,7 +16,7 @@ import { toError } from '@/utils';
import type { InviteEmailData, PasswordResetData, SendEmailResult } from './Interfaces';
import { NodeMailer } from './NodeMailer';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
type Template = HandlebarsTemplateDelegate<unknown>;
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 { WorkflowExecutionService } from './workflows/workflowExecution.service';
import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
import { EventService } from './eventbus/event.service';
import { EventService } from './events/event.service';
import { GlobalConfig } from '@n8n/config';
import { SubworkflowPolicyChecker } from './subworkflows/subworkflow-policy-checker.service';

View file

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

View file

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

View file

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

View file

@ -23,7 +23,7 @@ import { initExpressionEvaluator } from '@/ExpressionEvaluator';
import { generateHostInstanceId } from '@db/utils/generators';
import { WorkflowHistoryManager } from '@/workflows/workflowHistory/workflowHistoryManager.ee';
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 {
protected logger = Container.get(Logger);

View file

@ -33,7 +33,7 @@ import { ExecutionService } from '@/executions/execution.service';
import { OwnershipService } from '@/services/ownership.service';
import { WorkflowRunner } from '@/WorkflowRunner';
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
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 { BaseCommand } from './BaseCommand';
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 {
static description = '\nStarts a n8n worker';
@ -286,7 +286,7 @@ export class Worker extends BaseCommand {
await Container.get(MessageEventBus).initialize({
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 { IExecutingWorkflowData } from '@/Interfaces';
import type { Telemetry } from '@/telemetry';
import type { EventService } from '@/eventbus/event.service';
import type { EventService } from '@/events/event.service';
describe('ConcurrencyControlService', () => {
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 { IExecutingWorkflowData } from '@/Interfaces';
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_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 { ApplicationError } from 'n8n-workflow';
import { UserRepository } from '@/databases/repositories/user.repository';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
@RestController()
export class AuthController {

View file

@ -13,7 +13,7 @@ import { Push } from '@/push';
import { CommunityPackagesService } from '@/services/communityPackages.service';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
const {
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 { InternalHooks } from '@/InternalHooks';
import { ExternalHooks } from '@/ExternalHooks';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
@RestController('/invitations')
export class InvitationController {

View file

@ -23,7 +23,7 @@ import { InternalHooks } from '@/InternalHooks';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { UserRepository } from '@/databases/repositories/user.repository';
import { isApiEnabled } from '@/PublicApi';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
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 { UnprocessableRequestError } from '@/errors/response-errors/unprocessable.error';
import { UserRepository } from '@/databases/repositories/user.repository';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
@RestController()
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
import { In, Not } from '@n8n/typeorm';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
@RestController('/projects')
export class ProjectController {

View file

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

View file

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

View file

@ -1,5 +1,5 @@
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) {
return {
@ -14,7 +14,7 @@ function toRedactable(userLike: UserLike) {
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
* 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 { SOURCE_CONTROL_DEFAULT_BRANCH } from './constants';
import type { ImportResult } from './types/importResult';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
import { getRepoType } from './sourceControlHelper.ee';
import { SourceControlGetStatus } from './types/sourceControlGetStatus';
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 { SourceControlWorkflowVersionId } from './types/sourceControlWorkflowVersionId';
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 { Logger } from '@/Logger';
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 { VariableCountLimitReachedError } from '@/errors/variable-count-limit-reached.error';
import { VariableValidationError } from '@/errors/variable-validation.error';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
@Service()
export class VariablesService {

View file

@ -3,7 +3,7 @@ import { ApplicationError } from 'n8n-workflow';
export class RedactableError extends ApplicationError {
constructor(fieldName: string, args: string) {
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 { AuditEventRelay } from '../audit-event-relay.service';
import type { MessageEventBus } from '../MessageEventBus/MessageEventBus';
import type { Event } from '../event.types';
import { EventService } from '../event.service';
import { LogStreamingEventRelay } from '@/events/log-streaming-event-relay';
import { EventService } from '@/events/event.service';
import type { INode, IRun, IWorkflowBase } from 'n8n-workflow';
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 eventService = new EventService();
const auditor = new AuditEventRelay(eventService, eventBus);
auditor.init();
new LogStreamingEventRelay(eventService, eventBus).init();
afterEach(() => {
jest.clearAllMocks();
@ -18,7 +17,7 @@ describe('AuditEventRelay', () => {
describe('workflow events', () => {
it('should log on `workflow-created` event', () => {
const event: Event['workflow-created'] = {
const event: RelayEventMap['workflow-created'] = {
user: {
id: '123',
email: 'john@n8n.io',
@ -52,7 +51,7 @@ describe('AuditEventRelay', () => {
});
it('should log on `workflow-deleted` event', () => {
const event: Event['workflow-deleted'] = {
const event: RelayEventMap['workflow-deleted'] = {
user: {
id: '456',
email: 'jane@n8n.io',
@ -80,7 +79,7 @@ describe('AuditEventRelay', () => {
});
it('should log on `workflow-saved` event', () => {
const event: Event['workflow-saved'] = {
const event: RelayEventMap['workflow-saved'] = {
user: {
id: '789',
email: 'alex@n8n.io',
@ -119,7 +118,7 @@ describe('AuditEventRelay', () => {
settings: {},
});
const event: Event['workflow-pre-execute'] = {
const event: RelayEventMap['workflow-pre-execute'] = {
executionId: 'exec123',
data: workflow,
};
@ -139,7 +138,7 @@ describe('AuditEventRelay', () => {
});
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',
userId: 'some-id',
workflow: mock<IWorkflowBase>({ id: 'some-id', name: 'some-name' }),
@ -208,7 +207,7 @@ describe('AuditEventRelay', () => {
describe('user events', () => {
it('should log on `user-updated` event', () => {
const event: Event['user-updated'] = {
const event: RelayEventMap['user-updated'] = {
user: {
id: 'user456',
email: 'updated@example.com',
@ -235,7 +234,7 @@ describe('AuditEventRelay', () => {
});
it('should log on `user-deleted` event', () => {
const event: Event['user-deleted'] = {
const event: RelayEventMap['user-deleted'] = {
user: {
id: '123',
email: 'john@n8n.io',
@ -262,7 +261,7 @@ describe('AuditEventRelay', () => {
describe('click events', () => {
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: {
id: 'user101',
email: 'user101@example.com',
@ -287,7 +286,7 @@ describe('AuditEventRelay', () => {
});
it('should log on `user-invite-email-click` event', () => {
const event: Event['user-invite-email-click'] = {
const event: RelayEventMap['user-invite-email-click'] = {
inviter: {
id: '123',
email: 'john@n8n.io',
@ -354,7 +353,7 @@ describe('AuditEventRelay', () => {
settings: {},
});
const event: Event['node-pre-execute'] = {
const event: RelayEventMap['node-pre-execute'] = {
executionId: 'exec456',
nodeName: 'HTTP Request',
workflow,
@ -399,7 +398,7 @@ describe('AuditEventRelay', () => {
settings: {},
});
const event: Event['node-post-execute'] = {
const event: RelayEventMap['node-post-execute'] = {
executionId: 'exec789',
nodeName: 'HTTP Response',
workflow,
@ -422,7 +421,7 @@ describe('AuditEventRelay', () => {
describe('credentials events', () => {
it('should log on `credentials-shared` event', () => {
const event: Event['credentials-shared'] = {
const event: RelayEventMap['credentials-shared'] = {
user: {
id: 'user123',
email: 'sharer@example.com',
@ -457,7 +456,7 @@ describe('AuditEventRelay', () => {
});
it('should log on `credentials-created` event', () => {
const event: Event['credentials-created'] = {
const event: RelayEventMap['credentials-created'] = {
user: {
id: 'user123',
email: 'user@example.com',
@ -494,7 +493,7 @@ describe('AuditEventRelay', () => {
describe('auth events', () => {
it('should log on `user-login-failed` event', () => {
const event: Event['user-login-failed'] = {
const event: RelayEventMap['user-login-failed'] = {
userEmail: 'user@example.com',
authenticationMethod: 'email',
reason: 'Invalid password',
@ -515,7 +514,7 @@ describe('AuditEventRelay', () => {
describe('community package events', () => {
it('should log on `community-package-updated` event', () => {
const event: Event['community-package-updated'] = {
const event: RelayEventMap['community-package-updated'] = {
user: {
id: 'user202',
email: 'packageupdater@example.com',
@ -552,7 +551,7 @@ describe('AuditEventRelay', () => {
});
it('should log on `community-package-installed` event', () => {
const event: Event['community-package-installed'] = {
const event: RelayEventMap['community-package-installed'] = {
user: {
id: 'user789',
email: 'admin@example.com',
@ -593,7 +592,7 @@ describe('AuditEventRelay', () => {
describe('email events', () => {
it('should log on `email-failed` event', () => {
const event: Event['email-failed'] = {
const event: RelayEventMap['email-failed'] = {
user: {
id: 'user789',
email: 'recipient@example.com',
@ -622,7 +621,7 @@ describe('AuditEventRelay', () => {
describe('public API events', () => {
it('should log on `public-api-key-created` event', () => {
const event: Event['public-api-key-created'] = {
const event: RelayEventMap['public-api-key-created'] = {
user: {
id: 'user101',
email: 'apiuser@example.com',
@ -650,7 +649,7 @@ describe('AuditEventRelay', () => {
describe('execution events', () => {
it('should log on `execution-throttled` event', () => {
const event: Event['execution-throttled'] = {
const event: RelayEventMap['execution-throttled'] = {
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 { MessageEventBus } from './MessageEventBus/MessageEventBus';
import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus';
import { Redactable } from '@/decorators/Redactable';
import { EventService } from './event.service';
import type { Event } from './event.types';
import { EventRelay } from '@/events/event-relay';
import type { RelayEventMap } from '@/events/relay-event-map';
import type { IWorkflowBase } from 'n8n-workflow';
import { EventService } from './event.service';
@Service()
export class AuditEventRelay {
export class LogStreamingEventRelay extends EventRelay {
constructor(
private readonly eventService: EventService,
readonly eventService: EventService,
private readonly eventBus: MessageEventBus,
) {}
) {
super(eventService);
}
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() {
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
*/
// #region Workflow
@Redactable()
private workflowCreated({ user, workflow }: Event['workflow-created']) {
private workflowCreated({ user, workflow }: RelayEventMap['workflow-created']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.workflow.created',
payload: {
@ -78,7 +64,7 @@ export class AuditEventRelay {
}
@Redactable()
private workflowDeleted({ user, workflowId }: Event['workflow-deleted']) {
private workflowDeleted({ user, workflowId }: RelayEventMap['workflow-deleted']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.workflow.deleted',
payload: { ...user, workflowId },
@ -86,7 +72,7 @@ export class AuditEventRelay {
}
@Redactable()
private workflowSaved({ user, workflow }: Event['workflow-saved']) {
private workflowSaved({ user, workflow }: RelayEventMap['workflow-saved']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.workflow.updated',
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 =
'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 payload = {
@ -155,11 +141,11 @@ export class AuditEventRelay {
});
}
/**
* Node
*/
// #endregion
private nodePreExecute({ workflow, executionId, nodeName }: Event['node-pre-execute']) {
// #region Node
private nodePreExecute({ workflow, executionId, nodeName }: RelayEventMap['node-pre-execute']) {
void this.eventBus.sendNodeEvent({
eventName: 'n8n.node.started',
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({
eventName: 'n8n.node.finished',
payload: {
@ -185,12 +171,12 @@ export class AuditEventRelay {
});
}
/**
* User
*/
// #endregion
// #region User
@Redactable()
private userDeleted({ user }: Event['user-deleted']) {
private userDeleted({ user }: RelayEventMap['user-deleted']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.deleted',
payload: user,
@ -198,7 +184,7 @@ export class AuditEventRelay {
}
@Redactable()
private userInvited({ user, targetUserId }: Event['user-invited']) {
private userInvited({ user, targetUserId }: RelayEventMap['user-invited']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.invited',
payload: { ...user, targetUserId },
@ -206,7 +192,7 @@ export class AuditEventRelay {
}
@Redactable()
private userReinvited({ user, targetUserId }: Event['user-reinvited']) {
private userReinvited({ user, targetUserId }: RelayEventMap['user-reinvited']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.reinvited',
payload: { ...user, targetUserId },
@ -214,19 +200,19 @@ export class AuditEventRelay {
}
@Redactable()
private userUpdated({ user, fieldsChanged }: Event['user-updated']) {
private userUpdated({ user, fieldsChanged }: RelayEventMap['user-updated']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.updated',
payload: { ...user, fieldsChanged },
});
}
/**
* Auth
*/
// #endregion
// #region Auth
@Redactable()
private userSignedUp({ user }: Event['user-signed-up']) {
private userSignedUp({ user }: RelayEventMap['user-signed-up']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.signedup',
payload: user,
@ -234,7 +220,7 @@ export class AuditEventRelay {
}
@Redactable()
private userLoggedIn({ user, authenticationMethod }: Event['user-logged-in']) {
private userLoggedIn({ user, authenticationMethod }: RelayEventMap['user-logged-in']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.login.success',
payload: { ...user, authenticationMethod },
@ -242,7 +228,7 @@ export class AuditEventRelay {
}
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({
eventName: 'n8n.audit.user.login.failed',
@ -250,13 +236,13 @@ export class AuditEventRelay {
});
}
/**
* Click
*/
// #endregion
// #region Click
@Redactable('inviter')
@Redactable('invitee')
private userInviteEmailClick(event: Event['user-invite-email-click']) {
private userInviteEmailClick(event: RelayEventMap['user-invite-email-click']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.invitation.accepted',
payload: event,
@ -264,7 +250,7 @@ export class AuditEventRelay {
}
@Redactable()
private userPasswordResetEmailClick({ user }: Event['user-password-reset-email-click']) {
private userPasswordResetEmailClick({ user }: RelayEventMap['user-password-reset-email-click']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.reset',
payload: user,
@ -272,19 +258,21 @@ export class AuditEventRelay {
}
@Redactable()
private userPasswordResetRequestClick({ user }: Event['user-password-reset-request-click']) {
private userPasswordResetRequestClick({
user,
}: RelayEventMap['user-password-reset-request-click']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.reset.requested',
payload: user,
});
}
/**
* Public API
*/
// #endregion
// #region Public API
@Redactable()
private publicApiKeyCreated({ user }: Event['public-api-key-created']) {
private publicApiKeyCreated({ user }: RelayEventMap['public-api-key-created']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.api.created',
payload: user,
@ -292,31 +280,31 @@ export class AuditEventRelay {
}
@Redactable()
private publicApiKeyDeleted({ user }: Event['public-api-key-deleted']) {
private publicApiKeyDeleted({ user }: RelayEventMap['public-api-key-deleted']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.api.deleted',
payload: user,
});
}
/**
* Emailing
*/
// #endregion
// #region Email
@Redactable()
private emailFailed({ user, messageType }: Event['email-failed']) {
private emailFailed({ user, messageType }: RelayEventMap['email-failed']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.email.failed',
payload: { ...user, messageType },
});
}
/**
* Credentials
*/
// #endregion
// #region Credentials
@Redactable()
private credentialsCreated({ user, ...rest }: Event['credentials-created']) {
private credentialsCreated({ user, ...rest }: RelayEventMap['credentials-created']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.credentials.created',
payload: { ...user, ...rest },
@ -324,7 +312,7 @@ export class AuditEventRelay {
}
@Redactable()
private credentialsDeleted({ user, ...rest }: Event['credentials-deleted']) {
private credentialsDeleted({ user, ...rest }: RelayEventMap['credentials-deleted']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.credentials.deleted',
payload: { ...user, ...rest },
@ -332,7 +320,7 @@ export class AuditEventRelay {
}
@Redactable()
private credentialsShared({ user, ...rest }: Event['credentials-shared']) {
private credentialsShared({ user, ...rest }: RelayEventMap['credentials-shared']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.credentials.shared',
payload: { ...user, ...rest },
@ -340,19 +328,22 @@ export class AuditEventRelay {
}
@Redactable()
private credentialsUpdated({ user, ...rest }: Event['credentials-updated']) {
private credentialsUpdated({ user, ...rest }: RelayEventMap['credentials-updated']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.user.credentials.updated',
payload: { ...user, ...rest },
});
}
/**
* Community package
*/
// #endregion
// #region Community package
@Redactable()
private communityPackageInstalled({ user, ...rest }: Event['community-package-installed']) {
private communityPackageInstalled({
user,
...rest
}: RelayEventMap['community-package-installed']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.package.installed',
payload: { ...user, ...rest },
@ -360,7 +351,7 @@ export class AuditEventRelay {
}
@Redactable()
private communityPackageUpdated({ user, ...rest }: Event['community-package-updated']) {
private communityPackageUpdated({ user, ...rest }: RelayEventMap['community-package-updated']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.package.updated',
payload: { ...user, ...rest },
@ -368,28 +359,32 @@ export class AuditEventRelay {
}
@Redactable()
private communityPackageDeleted({ user, ...rest }: Event['community-package-deleted']) {
private communityPackageDeleted({ user, ...rest }: RelayEventMap['community-package-deleted']) {
void this.eventBus.sendAuditEvent({
eventName: 'n8n.audit.package.deleted',
payload: { ...user, ...rest },
});
}
/**
* Execution
*/
// #endregion
private executionThrottled({ executionId }: Event['execution-throttled']) {
// #region Execution
private executionThrottled({ executionId }: RelayEventMap['execution-throttled']) {
void this.eventBus.sendExecutionEvent({
eventName: 'n8n.execution.throttled',
payload: { executionId },
});
}
private executionStartedDuringBootup({ executionId }: Event['execution-started-during-bootup']) {
private executionStartedDuringBootup({
executionId,
}: RelayEventMap['execution-started-during-bootup']) {
void this.eventBus.sendExecutionEvent({
eventName: 'n8n.execution.started-during-bootup',
payload: { executionId },
});
}
// #endregion
}

View file

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

View file

@ -1,7 +1,7 @@
import { Service } from 'typedi';
import { EventService } from '@/eventbus/event.service';
import type { Event } from '@/eventbus/event.types';
import { Telemetry } from '.';
import { EventService } from '@/events/event.service';
import type { RelayEventMap } from '@/events/relay-event-map';
import { Telemetry } from '../telemetry';
import config from '@/config';
import os from 'node:os';
import { License } from '@/License';
@ -16,11 +16,12 @@ import { SharedWorkflowRepository } from '@/databases/repositories/sharedWorkflo
import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository';
import type { IExecutionTrackProperties } from '@/Interfaces';
import { determineFinalExecutionStatus } from '@/executionLifecycleHooks/shared/sharedHookFunctions';
import { EventRelay } from './event-relay';
@Service()
export class TelemetryEventRelay {
export class TelemetryEventRelay extends EventRelay {
constructor(
private readonly eventService: EventService,
readonly eventService: EventService,
private readonly telemetry: Telemetry,
private readonly license: License,
private readonly globalConfig: GlobalConfig,
@ -28,106 +29,63 @@ export class TelemetryEventRelay {
private readonly nodeTypes: NodeTypes,
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
private readonly projectRelationRepository: ProjectRelationRepository,
) {}
) {
super(eventService);
}
async init() {
if (!config.getEnv('diagnostics.enabled')) return;
await this.telemetry.init();
this.setupHandlers();
}
private setupHandlers() {
this.eventService.on('server-started', async () => await this.serverStarted());
this.eventService.on('team-project-updated', (event) => this.teamProjectUpdated(event));
this.eventService.on('team-project-deleted', (event) => this.teamProjectDeleted(event));
this.eventService.on('team-project-created', (event) => this.teamProjectCreated(event));
this.eventService.on('source-control-settings-updated', (event) =>
this.sourceControlSettingsUpdated(event),
);
this.eventService.on('source-control-user-started-pull-ui', (event) =>
this.sourceControlUserStartedPullUi(event),
);
this.eventService.on('source-control-user-finished-pull-ui', (event) =>
this.sourceControlUserFinishedPullUi(event),
);
this.eventService.on('source-control-user-pulled-api', (event) =>
this.sourceControlUserPulledApi(event),
);
this.eventService.on('source-control-user-started-push-ui', (event) =>
this.sourceControlUserStartedPushUi(event),
);
this.eventService.on('source-control-user-finished-push-ui', (event) =>
this.sourceControlUserFinishedPushUi(event),
);
this.eventService.on('license-renewal-attempted', (event) => {
this.licenseRenewalAttempted(event);
});
this.eventService.on('variable-created', () => this.variableCreated());
this.eventService.on('external-secrets-provider-settings-saved', (event) => {
this.externalSecretsProviderSettingsSaved(event);
});
this.eventService.on('public-api-invoked', (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);
this.setupListeners({
'team-project-updated': (event) => this.teamProjectUpdated(event),
'team-project-deleted': (event) => this.teamProjectDeleted(event),
'team-project-created': (event) => this.teamProjectCreated(event),
'source-control-settings-updated': (event) => this.sourceControlSettingsUpdated(event),
'source-control-user-started-pull-ui': (event) => this.sourceControlUserStartedPullUi(event),
'source-control-user-finished-pull-ui': (event) =>
this.sourceControlUserFinishedPullUi(event),
'source-control-user-pulled-api': (event) => this.sourceControlUserPulledApi(event),
'source-control-user-started-push-ui': (event) => this.sourceControlUserStartedPushUi(event),
'source-control-user-finished-push-ui': (event) =>
this.sourceControlUserFinishedPushUi(event),
'license-renewal-attempted': (event) => this.licenseRenewalAttempted(event),
'variable-created': () => this.variableCreated(),
'external-secrets-provider-settings-saved': (event) =>
this.externalSecretsProviderSettingsSaved(event),
'public-api-invoked': (event) => this.publicApiInvoked(event),
'public-api-key-created': (event) => this.publicApiKeyCreated(event),
'public-api-key-deleted': (event) => this.publicApiKeyDeleted(event),
'community-package-installed': (event) => this.communityPackageInstalled(event),
'community-package-updated': (event) => this.communityPackageUpdated(event),
'community-package-deleted': (event) => this.communityPackageDeleted(event),
'credentials-created': (event) => this.credentialsCreated(event),
'credentials-shared': (event) => this.credentialsShared(event),
'credentials-updated': (event) => this.credentialsUpdated(event),
'credentials-deleted': (event) => this.credentialsDeleted(event),
'ldap-general-sync-finished': (event) => this.ldapGeneralSyncFinished(event),
'ldap-settings-updated': (event) => this.ldapSettingsUpdated(event),
'ldap-login-sync-failed': (event) => this.ldapLoginSyncFailed(event),
'login-failed-due-to-ldap-disabled': (event) => this.loginFailedDueToLdapDisabled(event),
'workflow-created': (event) => this.workflowCreated(event),
'workflow-deleted': (event) => this.workflowDeleted(event),
'workflow-saved': async (event) => await this.workflowSaved(event),
'server-started': async () => await this.serverStarted(),
'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', {
user_id: userId,
role,
@ -143,7 +101,7 @@ export class TelemetryEventRelay {
projectId,
removalType,
targetProjectId,
}: Event['team-project-deleted']) {
}: RelayEventMap['team-project-deleted']) {
this.telemetry.track('User deleted project', {
user_id: userId,
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', {
user_id: userId,
role,
});
}
// #endregion
// #region Source control
private sourceControlSettingsUpdated({
branchName,
readOnlyInstance,
repoType,
connected,
}: Event['source-control-settings-updated']) {
}: RelayEventMap['source-control-settings-updated']) {
this.telemetry.track('User updated source control settings', {
branch_name: branchName,
read_only_instance: readOnlyInstance,
@ -178,7 +140,7 @@ export class TelemetryEventRelay {
workflowUpdates,
workflowConflicts,
credConflicts,
}: Event['source-control-user-started-pull-ui']) {
}: RelayEventMap['source-control-user-started-pull-ui']) {
this.telemetry.track('User started pull via UI', {
workflow_updates: workflowUpdates,
workflow_conflicts: workflowConflicts,
@ -188,7 +150,7 @@ export class TelemetryEventRelay {
private sourceControlUserFinishedPullUi({
workflowUpdates,
}: Event['source-control-user-finished-pull-ui']) {
}: RelayEventMap['source-control-user-finished-pull-ui']) {
this.telemetry.track('User finished pull via UI', {
workflow_updates: workflowUpdates,
});
@ -197,7 +159,7 @@ export class TelemetryEventRelay {
private sourceControlUserPulledApi({
workflowUpdates,
forced,
}: Event['source-control-user-pulled-api']) {
}: RelayEventMap['source-control-user-pulled-api']) {
console.log('source-control-user-pulled-api', {
workflow_updates: workflowUpdates,
forced,
@ -214,7 +176,7 @@ export class TelemetryEventRelay {
credsEligible,
credsEligibleWithConflicts,
variablesEligible,
}: Event['source-control-user-started-push-ui']) {
}: RelayEventMap['source-control-user-started-push-ui']) {
this.telemetry.track('User started push via UI', {
workflows_eligible: workflowsEligible,
workflows_eligible_with_conflicts: workflowsEligibleWithConflicts,
@ -229,7 +191,7 @@ export class TelemetryEventRelay {
workflowsPushed,
credsPushed,
variablesPushed,
}: Event['source-control-user-finished-push-ui']) {
}: RelayEventMap['source-control-user-finished-push-ui']) {
this.telemetry.track('User finished push via UI', {
workflows_eligible: workflowsEligible,
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', {
success,
});
}
// #endregion
// #region Variable
private variableCreated() {
this.telemetry.track('User created variable');
}
// #endregion
// #region External secrets
private externalSecretsProviderSettingsSaved({
userId,
vaultType,
isValid,
isNew,
errorMessage,
}: Event['external-secrets-provider-settings-saved']) {
}: RelayEventMap['external-secrets-provider-settings-saved']) {
this.telemetry.track('User updated external secrets settings', {
user_id: userId,
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', {
user_id: userId,
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;
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;
this.telemetry.track('API key deleted', {
@ -291,6 +274,10 @@ export class TelemetryEventRelay {
});
}
// #endregion
// #region Community package
private communityPackageInstalled({
user,
inputString,
@ -301,7 +288,7 @@ export class TelemetryEventRelay {
packageAuthor,
packageAuthorEmail,
failureReason,
}: Event['community-package-installed']) {
}: RelayEventMap['community-package-installed']) {
this.telemetry.track('cnr package install finished', {
user_id: user.id,
input_string: inputString,
@ -323,7 +310,7 @@ export class TelemetryEventRelay {
packageNodeNames,
packageAuthor,
packageAuthorEmail,
}: Event['community-package-updated']) {
}: RelayEventMap['community-package-updated']) {
this.telemetry.track('cnr package updated', {
user_id: user.id,
package_name: packageName,
@ -342,7 +329,7 @@ export class TelemetryEventRelay {
packageNodeNames,
packageAuthor,
packageAuthorEmail,
}: Event['community-package-deleted']) {
}: RelayEventMap['community-package-deleted']) {
this.telemetry.track('cnr package deleted', {
user_id: user.id,
package_name: packageName,
@ -353,13 +340,17 @@ export class TelemetryEventRelay {
});
}
// #endregion
// #region Credentials
private credentialsCreated({
user,
credentialType,
credentialId,
projectId,
projectType,
}: Event['credentials-created']) {
}: RelayEventMap['credentials-created']) {
this.telemetry.track('User created credentials', {
user_id: user.id,
credential_type: credentialType,
@ -376,7 +367,7 @@ export class TelemetryEventRelay {
userIdSharer,
userIdsShareesAdded,
shareesRemoved,
}: Event['credentials-shared']) {
}: RelayEventMap['credentials-shared']) {
this.telemetry.track('User updated cred sharing', {
user_id: user.id,
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', {
user_id: user.id,
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', {
user_id: user.id,
credential_type: credentialType,
@ -403,12 +402,16 @@ export class TelemetryEventRelay {
});
}
// #endregion
// #region LDAP
private ldapGeneralSyncFinished({
type,
succeeded,
usersSynced,
error,
}: Event['ldap-general-sync-finished']) {
}: RelayEventMap['ldap-general-sync-finished']) {
this.telemetry.track('Ldap general sync finished', {
type,
succeeded,
@ -430,7 +433,7 @@ export class TelemetryEventRelay {
synchronizationInterval,
loginLabel,
loginEnabled,
}: Event['ldap-settings-updated']) {
}: RelayEventMap['ldap-settings-updated']) {
this.telemetry.track('User updated Ldap settings', {
user_id: userId,
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 });
}
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 });
}
// #endregion
// #region Workflow
private workflowCreated({
user,
workflow,
publicApi,
projectId,
projectType,
}: Event['workflow-created']) {
}: RelayEventMap['workflow-created']) {
const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes);
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', {
user_id: user.id,
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 { 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
private async workflowPostExecute({ workflow, runData, userId }: Event['workflow-post-execute']) {
private async workflowPostExecute({
workflow,
runData,
userId,
}: RelayEventMap['workflow-post-execute']) {
if (!workflow.id) {
return;
}
@ -725,4 +674,74 @@ export class TelemetryEventRelay {
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 type { QueueRecoverySettings } from './execution.types';
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.

View file

@ -3,7 +3,7 @@ import axios from 'axios';
import { Logger } from '@/Logger';
import { License } from '@/License';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
import type { User } from '@db/entities/User';
import { WorkflowRepository } from '@db/repositories/workflow.repository';
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 type { UserRequest } from '@/requests';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
@Service()
export class UserService {

View file

@ -27,7 +27,7 @@ import {
import { SamlService } from '../saml.service.ee';
import { SamlConfiguration } from '../types/requests';
import { getInitSSOFormView } from '../views/initSsoPost';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
@RestController('/sso/saml')
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
import { In } from '@n8n/typeorm';
import { SharedWorkflow } from '@/databases/entities/SharedWorkflow';
import { EventService } from '@/eventbus/event.service';
import { EventService } from '@/events/event.service';
@Service()
export class WorkflowService {

View file

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

View file

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

View file

@ -15,7 +15,7 @@ import { type JobQueue, Queue } from '@/Queue';
import { setupTestCommand } from '@test-integration/utils/testCommand';
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('binaryDataManager.availableModes', 'filesystem');
@ -26,7 +26,7 @@ const externalHooks = mockInstance(ExternalHooks);
const externalSecretsManager = mockInstance(ExternalSecretsManager);
const license = mockInstance(License);
const messageEventBus = mockInstance(MessageEventBus);
const auditEventRelay = mockInstance(AuditEventRelay);
const logStreamingEventRelay = mockInstance(LogStreamingEventRelay);
const orchestrationHandlerWorkerService = mockInstance(OrchestrationHandlerWorkerService);
const queue = mockInstance(Queue);
const orchestrationWorkerService = mockInstance(OrchestrationWorkerService);
@ -45,7 +45,7 @@ test('worker initializes all its components', async () => {
expect(externalHooks.init).toHaveBeenCalledTimes(1);
expect(externalSecretsManager.init).toHaveBeenCalledTimes(1);
expect(messageEventBus.initialize).toHaveBeenCalledTimes(1);
expect(auditEventRelay.init).toHaveBeenCalledTimes(1);
expect(logStreamingEventRelay.init).toHaveBeenCalledTimes(1);
expect(queue.init).toHaveBeenCalledTimes(1);
expect(queue.process).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 * as testDb from '../testDb';
import { TelemetryEventRelay } from '@/telemetry/telemetry-event-relay.service';
import { TelemetryEventRelay } from '@/events/telemetry-event-relay';
import { mockInstance } from '@test/mocking';
export const setupTestCommand = <T extends BaseCommand>(Command: Class<T>) => {

View file

@ -1,6 +1,6 @@
import { LicenseErrors, LicenseService } from '@/license/license.service';
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 { TEntitlement } from '@n8n_io/license-sdk';
import { mock } from 'jest-mock-extended';