refactor(core): Decouple projects telemetry (no-changelog) (#10081)

This commit is contained in:
Iván Ovejero 2024-07-17 11:56:27 +02:00 committed by GitHub
parent ab5688c582
commit 8b2f76b92e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 105 additions and 43 deletions

View file

@ -16,7 +16,7 @@ import { InstanceSettings } from 'n8n-core';
import config from '@/config';
import { N8N_VERSION } from '@/constants';
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 { WorkflowRepository } from '@db/repositories/workflow.repository';
import { determineFinalExecutionStatus } from '@/executionLifecycleHooks/shared/sharedHookFunctions';
@ -30,7 +30,6 @@ import { EventsService } from '@/services/events.service';
import { NodeTypes } from '@/NodeTypes';
import { Telemetry } from '@/telemetry';
import type { Project } from '@db/entities/Project';
import type { ProjectRole } from '@db/entities/ProjectRelation';
import { ProjectRelationRepository } from './databases/repositories/projectRelation.repository';
import { SharedCredentialsRepository } from './databases/repositories/sharedCredentials.repository';
import { MessageEventBus } from './eventbus/MessageEventBus/MessageEventBus';
@ -834,31 +833,4 @@ export class InternalHooks {
}): Promise<void> {
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 });
}
}

View file

@ -23,6 +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';
export abstract class BaseCommand extends Command {
protected logger = Container.get(Logger);
@ -111,6 +112,7 @@ export abstract class BaseCommand extends Command {
await Container.get(PostHogClient).init();
await Container.get(InternalHooks).init();
await Container.get(TelemetryEventRelay).init();
}
protected setInstanceType(instanceType: N8nInstanceType) {

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 { InternalHooks } from '@/InternalHooks';
import { EventRelay } from '@/eventbus/event-relay.service';
@RestController('/projects')
export class ProjectController {
@ -31,7 +31,7 @@ export class ProjectController {
private readonly projectsService: ProjectService,
private readonly roleService: RoleService,
private readonly projectRepository: ProjectRepository,
private readonly internalHooks: InternalHooks,
private readonly eventRelay: EventRelay,
) {}
@Get('/')
@ -52,8 +52,8 @@ export class ProjectController {
try {
const project = await this.projectsService.createTeamProject(req.body.name, req.user);
void this.internalHooks.onTeamProjectCreated({
user_id: req.user.id,
this.eventRelay.emit('team-project-created', {
userId: req.user.id,
role: req.user.role,
});
@ -195,11 +195,11 @@ export class ProjectController {
throw e;
}
void this.internalHooks.onTeamProjectUpdated({
user_id: req.user.id,
this.eventRelay.emit('team-project-updated', {
userId: req.user.id,
role: req.user.role,
members: req.body.relations.map(({ userId, role }) => ({ user_id: userId, role })),
project_id: req.params.projectId,
members: req.body.relations,
projectId: req.params.projectId,
});
}
}
@ -211,12 +211,12 @@ export class ProjectController {
migrateToProject: req.query.transferId,
});
void this.internalHooks.onTeamProjectDeleted({
user_id: req.user.id,
this.eventRelay.emit('team-project-deleted', {
userId: req.user.id,
role: req.user.role,
project_id: req.params.projectId,
removal_type: req.query.transferId !== undefined ? 'transfer' : 'delete',
target_project_id: req.query.transferId,
projectId: req.params.projectId,
removalType: req.query.transferId !== undefined ? 'transfer' : 'delete',
targetProjectId: req.query.transferId,
});
}
}

View file

@ -1,5 +1,7 @@
import type { AuthenticationMethod, IWorkflowBase } from 'n8n-workflow';
import type { IWorkflowExecutionDataProcess } from '@/Interfaces';
import type { ProjectRole } from '@/databases/entities/ProjectRelation';
import type { GlobalRole } from '@/databases/entities/User';
export type UserLike = {
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 = {
'workflow-created': {
@ -190,4 +192,27 @@ export type Event = {
'execution-started-during-bootup': {
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;
};
};

View 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,
});
}
}

View file

@ -4,6 +4,8 @@ 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 { mockInstance } from '@test/mocking';
export const setupTestCommand = <T extends BaseCommand>(Command: Class<T>) => {
const config = mock<Config>();
@ -19,6 +21,7 @@ export const setupTestCommand = <T extends BaseCommand>(Command: Class<T>) => {
beforeEach(() => {
jest.clearAllMocks();
mockInstance(TelemetryEventRelay);
});
afterAll(async () => {