diff --git a/packages/cli/package.json b/packages/cli/package.json index d2e29532ac..11337d7ab5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -99,7 +99,7 @@ "@n8n/n8n-nodes-langchain": "workspace:*", "@n8n/permissions": "workspace:*", "@n8n/typeorm": "0.3.20-10", - "@n8n_io/license-sdk": "2.12.0", + "@n8n_io/license-sdk": "2.13.0", "@oclif/core": "3.26.6", "@pinecone-database/pinecone": "2.1.0", "@rudderstack/rudder-sdk-node": "2.0.7", diff --git a/packages/cli/src/License.ts b/packages/cli/src/License.ts index 94e6b1b96f..6dfd542534 100644 --- a/packages/cli/src/License.ts +++ b/packages/cli/src/License.ts @@ -84,6 +84,9 @@ export class License { const collectUsageMetrics = isMainInstance ? async () => await this.usageMetricsService.collectUsageMetrics() : async () => []; + const collectPassthroughData = isMainInstance + ? async () => await this.usageMetricsService.getActiveWorkflowIds() + : async () => ({}); const renewalEnabled = this.renewalEnabled(instanceType); @@ -101,6 +104,7 @@ export class License { saveCertStr, deviceFingerprint: () => this.instanceSettings.instanceId, collectUsageMetrics, + collectPassthroughData, onFeatureChange, }); diff --git a/packages/cli/src/services/usageMetrics.service.ts b/packages/cli/src/services/usageMetrics.service.ts index bf60072f6b..f9d925d090 100644 --- a/packages/cli/src/services/usageMetrics.service.ts +++ b/packages/cli/src/services/usageMetrics.service.ts @@ -1,9 +1,13 @@ import { UsageMetricsRepository } from '@/databases/repositories/usageMetrics.repository'; import { Service } from 'typedi'; +import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; @Service() export class UsageMetricsService { - constructor(private readonly usageMetricsRepository: UsageMetricsRepository) {} + constructor( + private readonly usageMetricsRepository: UsageMetricsRepository, + private readonly workflowRepository: WorkflowRepository, + ) {} async collectUsageMetrics() { const { @@ -26,4 +30,10 @@ export class UsageMetricsService { { name: 'manualExecutions', value: manualExecutions }, ]; } + + async getActiveWorkflowIds() { + return { + activeWorkflowIds: await this.workflowRepository.getActiveIds(), + }; + } } diff --git a/packages/cli/test/integration/database/repositories/workflow.repository.test.ts b/packages/cli/test/integration/database/repositories/workflow.repository.test.ts index d9e1ea22fd..2f7acbb6d2 100644 --- a/packages/cli/test/integration/database/repositories/workflow.repository.test.ts +++ b/packages/cli/test/integration/database/repositories/workflow.repository.test.ts @@ -3,7 +3,11 @@ import Container from 'typedi'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import * as testDb from '../../shared/testDb'; -import { createWorkflowWithTrigger, getAllWorkflows } from '../../shared/db/workflows'; +import { + createWorkflowWithTrigger, + createWorkflow, + getAllWorkflows, +} from '../../shared/db/workflows'; describe('WorkflowRepository', () => { beforeAll(async () => { @@ -67,4 +71,27 @@ describe('WorkflowRepository', () => { expect(after).toMatchObject([{ active: false }, { active: false }]); }); }); + + describe('getActiveIds', () => { + it('should return active workflow IDs', async () => { + // + // ARRANGE + // + const workflows = await Promise.all([ + createWorkflow({ active: true }), + createWorkflow({ active: false }), + createWorkflow({ active: false }), + ]); + + // + // ACT + // + const activeIds = await Container.get(WorkflowRepository).getActiveIds(); + + // + // ASSERT + // + expect(activeIds).toEqual([workflows[0].id]); + }); + }); }); diff --git a/packages/cli/test/integration/usage-metrics.service.test.ts b/packages/cli/test/integration/usage-metrics.service.test.ts new file mode 100644 index 0000000000..a620f16376 --- /dev/null +++ b/packages/cli/test/integration/usage-metrics.service.test.ts @@ -0,0 +1,32 @@ +import { mock } from 'jest-mock-extended'; +import { UsageMetricsService } from '@/services/usageMetrics.service'; +import type { WorkflowRepository } from '@/databases/repositories/workflow.repository'; +import type { UsageMetricsRepository } from '@/databases/repositories/usageMetrics.repository'; + +describe('UsageMetricsService', () => { + const workflowRepository = mock(); + const usageMetricsService = new UsageMetricsService( + mock(), + workflowRepository, + ); + + describe('getActiveWorkflowIds', () => { + test('should return active workflow IDs', async () => { + /** + * Arrange + */ + const activeWorkflowIds = ['1', '2']; + workflowRepository.getActiveIds.mockResolvedValue(activeWorkflowIds); + + /** + * Act + */ + const result = await usageMetricsService.getActiveWorkflowIds(); + + /** + * Assert + */ + expect(result).toEqual({ activeWorkflowIds }); + }); + }); +}); diff --git a/packages/cli/test/unit/License.test.ts b/packages/cli/test/unit/License.test.ts index 317bfe1e6a..6d7311656c 100644 --- a/packages/cli/test/unit/License.test.ts +++ b/packages/cli/test/unit/License.test.ts @@ -48,6 +48,7 @@ describe('License', () => { saveCertStr: expect.any(Function), onFeatureChange: expect.any(Function), collectUsageMetrics: expect.any(Function), + collectPassthroughData: expect.any(Function), server: MOCK_SERVER_URL, tenantId: 1, }); @@ -68,6 +69,7 @@ describe('License', () => { saveCertStr: expect.any(Function), onFeatureChange: expect.any(Function), collectUsageMetrics: expect.any(Function), + collectPassthroughData: expect.any(Function), server: MOCK_SERVER_URL, tenantId: 1, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95e37deb5d..fc7e7bc978 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -550,8 +550,8 @@ importers: specifier: 0.3.20-10 version: 0.3.20-10(@sentry/node@7.87.0)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.10.0)(pg@8.11.3)(redis@4.6.13)(sqlite3@5.1.7) '@n8n_io/license-sdk': - specifier: 2.12.0 - version: 2.12.0 + specifier: 2.13.0 + version: 2.13.0 '@oclif/core': specifier: 3.26.6 version: 3.26.6 @@ -4124,8 +4124,8 @@ packages: engines: {node: '>=18.10', pnpm: '>=8.6.12'} hasBin: true - '@n8n_io/license-sdk@2.12.0': - resolution: {integrity: sha512-hxXdaFlzd1moR5NWPCdGiKHyIuDTuiM8AYcH5qo2/Cbu93C8za8a0x19q5gZ1ahADRPfO48k9eYMTjxGhqNkBg==} + '@n8n_io/license-sdk@2.13.0': + resolution: {integrity: sha512-iVER9RjR6pP4ujceG7rSMoHU0IMI5HvwNHhC8ezq1VwbRq8W1ecYQpTbIrUTgK6gNMyeLRfySkNlVGejKXJ3MQ==} engines: {node: '>=18.12.1'} '@n8n_io/riot-tmpl@4.0.0': @@ -17121,7 +17121,7 @@ snapshots: acorn: 8.11.2 acorn-walk: 8.2.0 - '@n8n_io/license-sdk@2.12.0': + '@n8n_io/license-sdk@2.13.0': dependencies: crypto-js: 4.2.0 node-machine-id: 1.1.12