mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
refactor(core): Decouple projects telemetry (no-changelog) (#10081)
This commit is contained in:
parent
ab5688c582
commit
8b2f76b92e
|
@ -16,7 +16,7 @@ import { InstanceSettings } from 'n8n-core';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { N8N_VERSION } from '@/constants';
|
import { N8N_VERSION } from '@/constants';
|
||||||
import type { AuthProviderType } from '@db/entities/AuthIdentity';
|
import type { AuthProviderType } from '@db/entities/AuthIdentity';
|
||||||
import type { GlobalRole, User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||||
import { determineFinalExecutionStatus } from '@/executionLifecycleHooks/shared/sharedHookFunctions';
|
import { determineFinalExecutionStatus } from '@/executionLifecycleHooks/shared/sharedHookFunctions';
|
||||||
|
@ -30,7 +30,6 @@ import { EventsService } from '@/services/events.service';
|
||||||
import { NodeTypes } from '@/NodeTypes';
|
import { NodeTypes } from '@/NodeTypes';
|
||||||
import { Telemetry } from '@/telemetry';
|
import { Telemetry } from '@/telemetry';
|
||||||
import type { Project } from '@db/entities/Project';
|
import type { Project } from '@db/entities/Project';
|
||||||
import type { ProjectRole } from '@db/entities/ProjectRelation';
|
|
||||||
import { ProjectRelationRepository } from './databases/repositories/projectRelation.repository';
|
import { ProjectRelationRepository } from './databases/repositories/projectRelation.repository';
|
||||||
import { SharedCredentialsRepository } from './databases/repositories/sharedCredentials.repository';
|
import { SharedCredentialsRepository } from './databases/repositories/sharedCredentials.repository';
|
||||||
import { MessageEventBus } from './eventbus/MessageEventBus/MessageEventBus';
|
import { MessageEventBus } from './eventbus/MessageEventBus/MessageEventBus';
|
||||||
|
@ -834,31 +833,4 @@ export class InternalHooks {
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
return await this.telemetry.track('User updated external secrets settings', saveData);
|
return await this.telemetry.track('User updated external secrets settings', saveData);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onTeamProjectCreated(data: { user_id: string; role: GlobalRole }) {
|
|
||||||
return await this.telemetry.track('User created project', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async onTeamProjectDeleted(data: {
|
|
||||||
user_id: string;
|
|
||||||
role: GlobalRole;
|
|
||||||
project_id: string;
|
|
||||||
removal_type: 'delete' | 'transfer';
|
|
||||||
target_project_id?: string;
|
|
||||||
}) {
|
|
||||||
return await this.telemetry.track('User deleted project', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async onTeamProjectUpdated(data: {
|
|
||||||
user_id: string;
|
|
||||||
role: GlobalRole;
|
|
||||||
project_id: string;
|
|
||||||
members: Array<{ user_id: string; role: ProjectRole }>;
|
|
||||||
}) {
|
|
||||||
return await this.telemetry.track('Project settings updated', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async onConcurrencyLimitHit({ threshold }: { threshold: number }) {
|
|
||||||
await this.telemetry.track('User hit concurrency limit', { threshold });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +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';
|
||||||
|
|
||||||
export abstract class BaseCommand extends Command {
|
export abstract class BaseCommand extends Command {
|
||||||
protected logger = Container.get(Logger);
|
protected logger = Container.get(Logger);
|
||||||
|
@ -111,6 +112,7 @@ export abstract class BaseCommand extends Command {
|
||||||
|
|
||||||
await Container.get(PostHogClient).init();
|
await Container.get(PostHogClient).init();
|
||||||
await Container.get(InternalHooks).init();
|
await Container.get(InternalHooks).init();
|
||||||
|
await Container.get(TelemetryEventRelay).init();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected setInstanceType(instanceType: N8nInstanceType) {
|
protected setInstanceType(instanceType: N8nInstanceType) {
|
||||||
|
|
|
@ -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 { InternalHooks } from '@/InternalHooks';
|
import { EventRelay } from '@/eventbus/event-relay.service';
|
||||||
|
|
||||||
@RestController('/projects')
|
@RestController('/projects')
|
||||||
export class ProjectController {
|
export class ProjectController {
|
||||||
|
@ -31,7 +31,7 @@ export class ProjectController {
|
||||||
private readonly projectsService: ProjectService,
|
private readonly projectsService: ProjectService,
|
||||||
private readonly roleService: RoleService,
|
private readonly roleService: RoleService,
|
||||||
private readonly projectRepository: ProjectRepository,
|
private readonly projectRepository: ProjectRepository,
|
||||||
private readonly internalHooks: InternalHooks,
|
private readonly eventRelay: EventRelay,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('/')
|
@Get('/')
|
||||||
|
@ -52,8 +52,8 @@ export class ProjectController {
|
||||||
try {
|
try {
|
||||||
const project = await this.projectsService.createTeamProject(req.body.name, req.user);
|
const project = await this.projectsService.createTeamProject(req.body.name, req.user);
|
||||||
|
|
||||||
void this.internalHooks.onTeamProjectCreated({
|
this.eventRelay.emit('team-project-created', {
|
||||||
user_id: req.user.id,
|
userId: req.user.id,
|
||||||
role: req.user.role,
|
role: req.user.role,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -195,11 +195,11 @@ export class ProjectController {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
void this.internalHooks.onTeamProjectUpdated({
|
this.eventRelay.emit('team-project-updated', {
|
||||||
user_id: req.user.id,
|
userId: req.user.id,
|
||||||
role: req.user.role,
|
role: req.user.role,
|
||||||
members: req.body.relations.map(({ userId, role }) => ({ user_id: userId, role })),
|
members: req.body.relations,
|
||||||
project_id: req.params.projectId,
|
projectId: req.params.projectId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,12 +211,12 @@ export class ProjectController {
|
||||||
migrateToProject: req.query.transferId,
|
migrateToProject: req.query.transferId,
|
||||||
});
|
});
|
||||||
|
|
||||||
void this.internalHooks.onTeamProjectDeleted({
|
this.eventRelay.emit('team-project-deleted', {
|
||||||
user_id: req.user.id,
|
userId: req.user.id,
|
||||||
role: req.user.role,
|
role: req.user.role,
|
||||||
project_id: req.params.projectId,
|
projectId: req.params.projectId,
|
||||||
removal_type: req.query.transferId !== undefined ? 'transfer' : 'delete',
|
removalType: req.query.transferId !== undefined ? 'transfer' : 'delete',
|
||||||
target_project_id: req.query.transferId,
|
targetProjectId: req.query.transferId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import type { AuthenticationMethod, IWorkflowBase } from 'n8n-workflow';
|
import type { AuthenticationMethod, IWorkflowBase } from 'n8n-workflow';
|
||||||
import type { IWorkflowExecutionDataProcess } from '@/Interfaces';
|
import type { IWorkflowExecutionDataProcess } from '@/Interfaces';
|
||||||
|
import type { ProjectRole } from '@/databases/entities/ProjectRelation';
|
||||||
|
import type { GlobalRole } from '@/databases/entities/User';
|
||||||
|
|
||||||
export type UserLike = {
|
export type UserLike = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -10,7 +12,7 @@ export type UserLike = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Events sent by services and consumed by relays, e.g. `AuditEventRelay`.
|
* Events sent by services and consumed by relays, e.g. `AuditEventRelay` and `TelemetryEventRelay`.
|
||||||
*/
|
*/
|
||||||
export type Event = {
|
export type Event = {
|
||||||
'workflow-created': {
|
'workflow-created': {
|
||||||
|
@ -190,4 +192,27 @@ export type Event = {
|
||||||
'execution-started-during-bootup': {
|
'execution-started-during-bootup': {
|
||||||
executionId: string;
|
executionId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
'team-project-updated': {
|
||||||
|
userId: string;
|
||||||
|
role: GlobalRole;
|
||||||
|
members: Array<{
|
||||||
|
userId: string;
|
||||||
|
role: ProjectRole;
|
||||||
|
}>;
|
||||||
|
projectId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
'team-project-deleted': {
|
||||||
|
userId: string;
|
||||||
|
role: GlobalRole;
|
||||||
|
projectId: string;
|
||||||
|
removalType: 'transfer' | 'delete';
|
||||||
|
targetProjectId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
'team-project-created': {
|
||||||
|
userId: string;
|
||||||
|
role: GlobalRole;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
60
packages/cli/src/telemetry/telemetry-event-relay.service.ts
Normal file
60
packages/cli/src/telemetry/telemetry-event-relay.service.ts
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import { Service } from 'typedi';
|
||||||
|
import { EventRelay } from '@/eventbus/event-relay.service';
|
||||||
|
import type { Event } from '@/eventbus/event.types';
|
||||||
|
import { Telemetry } from '.';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class TelemetryEventRelay {
|
||||||
|
constructor(
|
||||||
|
private readonly eventRelay: EventRelay,
|
||||||
|
private readonly telemetry: Telemetry,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (!config.getEnv('diagnostics.enabled')) return;
|
||||||
|
|
||||||
|
await this.telemetry.init();
|
||||||
|
|
||||||
|
this.setupHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupHandlers() {
|
||||||
|
this.eventRelay.on('team-project-updated', (event) => this.teamProjectUpdated(event));
|
||||||
|
this.eventRelay.on('team-project-deleted', (event) => this.teamProjectDeleted(event));
|
||||||
|
this.eventRelay.on('team-project-created', (event) => this.teamProjectCreated(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
private teamProjectUpdated({ userId, role, members, projectId }: Event['team-project-updated']) {
|
||||||
|
void this.telemetry.track('Project settings updated', {
|
||||||
|
user_id: userId,
|
||||||
|
role,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
members: members.map(({ userId: user_id, role }) => ({ user_id, role })),
|
||||||
|
project_id: projectId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private teamProjectDeleted({
|
||||||
|
userId,
|
||||||
|
role,
|
||||||
|
projectId,
|
||||||
|
removalType,
|
||||||
|
targetProjectId,
|
||||||
|
}: Event['team-project-deleted']) {
|
||||||
|
void this.telemetry.track('User deleted project', {
|
||||||
|
user_id: userId,
|
||||||
|
role,
|
||||||
|
project_id: projectId,
|
||||||
|
removal_type: removalType,
|
||||||
|
target_project_id: targetProjectId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private teamProjectCreated({ userId, role }: Event['team-project-created']) {
|
||||||
|
void this.telemetry.track('User created project', {
|
||||||
|
user_id: userId,
|
||||||
|
role,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ 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 { mockInstance } from '@test/mocking';
|
||||||
|
|
||||||
export const setupTestCommand = <T extends BaseCommand>(Command: Class<T>) => {
|
export const setupTestCommand = <T extends BaseCommand>(Command: Class<T>) => {
|
||||||
const config = mock<Config>();
|
const config = mock<Config>();
|
||||||
|
@ -19,6 +21,7 @@ export const setupTestCommand = <T extends BaseCommand>(Command: Class<T>) => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
mockInstance(TelemetryEventRelay);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
Loading…
Reference in a new issue