mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-16 21:19:43 +02:00
refactor(core): Decouple data table events
This commit is contained in:
parent
88a2531558
commit
e9b13630e1
|
|
@ -1,6 +1,8 @@
|
|||
import { EventService, type EventMap } from '@n8n/backend-services';
|
||||
import { expectTypeOf } from 'vitest';
|
||||
|
||||
import type { DataTableEventMap } from '@/modules/data-table/data-table-event-map';
|
||||
|
||||
import type { AiEventMap } from '../maps/ai.event-map';
|
||||
import type { ExecutionDataEventMap } from '../maps/execution-data.event-map';
|
||||
import type { InstanceAiEventMap } from '../maps/instance-ai.event-map';
|
||||
|
|
@ -21,7 +23,8 @@ describe('CLI event map', () => {
|
|||
McpPostSaveMetricsEventMap &
|
||||
WorkflowPublicationMetricsEventMap &
|
||||
PollTriggerMetricsEventMap &
|
||||
SystemTaskMetricsEventMap;
|
||||
SystemTaskMetricsEventMap &
|
||||
DataTableEventMap;
|
||||
|
||||
expectTypeOf<EventMap>().toMatchTypeOf<CliEventMap>();
|
||||
expectTypeOf<CliEventMap>().toMatchTypeOf<EventMap>();
|
||||
|
|
|
|||
|
|
@ -271,6 +271,20 @@ describe('TelemetryEventRelay', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('data table events', () => {
|
||||
it('should track when the storage limit is reached', () => {
|
||||
eventService.emit('data-table-storage-limit-hit', {
|
||||
totalBytes: 100,
|
||||
maxBytes: 100,
|
||||
});
|
||||
|
||||
expect(telemetry.track).toHaveBeenCalledWith('User hit data table storage limit', {
|
||||
total_bytes: 100,
|
||||
max_bytes: 100,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('project events', () => {
|
||||
it('should track on `team-project-updated` event', () => {
|
||||
const event: RelayEventMap['team-project-updated'] = {
|
||||
|
|
|
|||
|
|
@ -1136,15 +1136,6 @@ export type RelayEventMap = {
|
|||
};
|
||||
// #endregion
|
||||
|
||||
// #region Data Tables
|
||||
|
||||
'data-table-deleted': {
|
||||
dataTableId: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Folders
|
||||
|
||||
'folder-deleted': {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
import { EventService } from '@n8n/backend-services';
|
||||
import { EventService, type EventMap } from '@n8n/backend-services';
|
||||
import { Service } from '@n8n/di';
|
||||
|
||||
import type { RelayEventMap } from '@/events/maps/relay.event-map';
|
||||
|
||||
@Service()
|
||||
export class EventRelay {
|
||||
constructor(readonly eventService: EventService) {}
|
||||
|
||||
protected setupListeners<EventNames extends keyof RelayEventMap>(
|
||||
protected setupListeners<EventNames extends keyof EventMap>(
|
||||
map: {
|
||||
[EventName in EventNames]?: (event: RelayEventMap[EventName]) => void | Promise<void>;
|
||||
[EventName in EventNames]?: (event: EventMap[EventName]) => void | Promise<void>;
|
||||
},
|
||||
) {
|
||||
for (const [eventName, handler] of Object.entries(map) as Array<
|
||||
[EventNames, (event: RelayEventMap[EventNames]) => void | Promise<void>]
|
||||
[EventNames, (event: EventMap[EventNames]) => void | Promise<void>]
|
||||
>) {
|
||||
this.eventService.on(eventName, async (event) => {
|
||||
await handler(event);
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ import type {
|
|||
import { NodeTypes } from '@/node-types';
|
||||
import { PostHogClient } from '@/posthog';
|
||||
import { OwnershipService } from '@/services/ownership.service';
|
||||
import type { DataTableEventMap } from '@/modules/data-table/data-table-event-map';
|
||||
|
||||
import { EventRelay } from './event-relay';
|
||||
import { Telemetry } from '../../telemetry';
|
||||
|
|
@ -216,6 +217,7 @@ export class TelemetryEventRelay extends EventRelay {
|
|||
await this.telemetry.init();
|
||||
|
||||
this.setupListeners({
|
||||
'data-table-storage-limit-hit': (event) => this.dataTableStorageLimitHit(event),
|
||||
'team-project-updated': (event) => this.teamProjectUpdated(event),
|
||||
'team-project-deleted': (event) => this.teamProjectDeleted(event),
|
||||
'team-project-created': (event) => this.teamProjectCreated(event),
|
||||
|
|
@ -341,6 +343,16 @@ export class TelemetryEventRelay extends EventRelay {
|
|||
});
|
||||
}
|
||||
|
||||
private dataTableStorageLimitHit({
|
||||
totalBytes,
|
||||
maxBytes,
|
||||
}: DataTableEventMap['data-table-storage-limit-hit']) {
|
||||
this.telemetry.track('User hit data table storage limit', {
|
||||
total_bytes: totalBytes,
|
||||
max_bytes: maxBytes,
|
||||
});
|
||||
}
|
||||
|
||||
// #region HITL
|
||||
|
||||
private hitlResponseActioned({
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { mockInstance } from '@n8n/backend-test-utils';
|
||||
import { EventService } from '@n8n/backend-services';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
import { Telemetry } from '@/telemetry';
|
||||
|
||||
import { DataTableSizeValidator } from '../data-table-size-validator.service';
|
||||
|
||||
describe('DataTableSizeValidator', () => {
|
||||
|
|
@ -16,9 +15,9 @@ describe('DataTableSizeValidator', () => {
|
|||
maxSize: 100 * 1024 * 1024,
|
||||
},
|
||||
});
|
||||
const telemetry = mockInstance(Telemetry);
|
||||
const eventService = mockInstance(EventService);
|
||||
beforeEach(() => {
|
||||
validator = new DataTableSizeValidator(globalConfig, telemetry);
|
||||
validator = new DataTableSizeValidator(globalConfig, eventService);
|
||||
fetchSizeFn = vi.fn();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
|
@ -46,6 +45,10 @@ describe('DataTableSizeValidator', () => {
|
|||
await expect(
|
||||
validator.validateSize(fetchSizeFn, new Date('2024-01-01T00:00:00Z')),
|
||||
).rejects.toThrow('Data table size limit exceeded: 150MB used, limit is 100MB');
|
||||
expect(eventService.emit).toHaveBeenCalledWith('data-table-storage-limit-hit', {
|
||||
totalBytes: 150 * 1024 * 1024,
|
||||
maxBytes: 100 * 1024 * 1024,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when size equals limit', async () => {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import {
|
|||
testModules,
|
||||
} from '@n8n/backend-test-utils';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { EventService } from '@n8n/backend-services';
|
||||
import { GLOBAL_MEMBER_ROLE, GLOBAL_OWNER_ROLE, type Project, type User } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
import { Telemetry } from '@/telemetry';
|
||||
import { createUser } from '@test-integration/db/users';
|
||||
|
||||
import { DataTableSizeValidator } from '../data-table-size-validator.service';
|
||||
|
|
@ -18,7 +18,7 @@ import { DataTableService } from '../data-table.service';
|
|||
import { DataTableValidationError } from '../errors/data-table-validation.error';
|
||||
|
||||
beforeAll(async () => {
|
||||
mockInstance(Telemetry);
|
||||
mockInstance(EventService);
|
||||
await testModules.loadModules(['data-table']);
|
||||
await testDb.init();
|
||||
});
|
||||
|
|
|
|||
16
packages/cli/src/modules/data-table/data-table-event-map.ts
Normal file
16
packages/cli/src/modules/data-table/data-table-event-map.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import type {} from '@n8n/backend-services';
|
||||
|
||||
export type DataTableEventMap = {
|
||||
'data-table-deleted': {
|
||||
dataTableId: string;
|
||||
projectId: string;
|
||||
};
|
||||
'data-table-storage-limit-hit': {
|
||||
totalBytes: number;
|
||||
maxBytes: number;
|
||||
};
|
||||
};
|
||||
|
||||
declare module '@n8n/backend-services' {
|
||||
interface EventMap extends DataTableEventMap {}
|
||||
}
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
import { EventService } from '@n8n/backend-services';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { Service } from '@n8n/di';
|
||||
import { toMb } from '@n8n/utils/number/bytes';
|
||||
import { DataTableSizeStatus, DataTablesSizeData } from 'n8n-workflow';
|
||||
|
||||
import { Telemetry } from '@/telemetry';
|
||||
|
||||
import { DataTableValidationError } from './errors/data-table-validation.error';
|
||||
|
||||
@Service()
|
||||
|
|
@ -15,7 +14,7 @@ export class DataTableSizeValidator {
|
|||
|
||||
constructor(
|
||||
private readonly globalConfig: GlobalConfig,
|
||||
private readonly telemetry: Telemetry,
|
||||
private readonly eventService: EventService,
|
||||
) {}
|
||||
|
||||
private shouldRefresh(now: Date): boolean {
|
||||
|
|
@ -59,9 +58,9 @@ export class DataTableSizeValidator {
|
|||
): Promise<void> {
|
||||
const size = await this.getCachedSizeData(fetchSizeFn, now);
|
||||
if (size.totalBytes >= this.globalConfig.dataTable.maxSize) {
|
||||
this.telemetry.track('User hit data table storage limit', {
|
||||
total_bytes: size.totalBytes,
|
||||
max_bytes: this.globalConfig.dataTable.maxSize,
|
||||
this.eventService.emit('data-table-storage-limit-hit', {
|
||||
totalBytes: size.totalBytes,
|
||||
maxBytes: this.globalConfig.dataTable.maxSize,
|
||||
});
|
||||
|
||||
throw new DataTableValidationError(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { EventService } from '@n8n/backend-services';
|
|||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
import type { RelayEventMap } from '@/events/maps/relay.event-map';
|
||||
import type { DataTableEventMap } from '@/modules/data-table/data-table-event-map';
|
||||
|
||||
import { FavoritesEventRelay } from '../favorites.event-relay';
|
||||
import type { FavoritesService } from '../favorites.service';
|
||||
|
|
@ -33,7 +34,7 @@ describe('FavoritesEventRelay', () => {
|
|||
|
||||
describe('data-table-deleted', () => {
|
||||
it('should delete favorites for the deleted data table', async () => {
|
||||
const event: RelayEventMap['data-table-deleted'] = {
|
||||
const event: DataTableEventMap['data-table-deleted'] = {
|
||||
dataTableId: 'dt1',
|
||||
projectId: 'proj1',
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user