diff --git a/src/lib/features/instance-stats/createInstanceStatsService.ts b/src/lib/features/instance-stats/createInstanceStatsService.ts index bf673dd5f3e5..f1718c149166 100644 --- a/src/lib/features/instance-stats/createInstanceStatsService.ts +++ b/src/lib/features/instance-stats/createInstanceStatsService.ts @@ -102,6 +102,12 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => { const trafficDataUsageStore = new TrafficDataUsageStore(db, getLogger); + const featureStrategiesStore = new FeatureStrategyStore( + db, + eventBus, + getLogger, + flagResolver, + ); const instanceStatsServiceStores = { featureToggleStore, userStore, @@ -118,27 +124,14 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => { apiTokenStore, clientMetricsStoreV2, featureStrategiesReadModel, - trafficDataUsageStore, - }; - const featureStrategiesStore = new FeatureStrategyStore( - db, - eventBus, - getLogger, - flagResolver, - ); - const versionServiceStores = { - ...instanceStatsServiceStores, featureStrategiesStore, + trafficDataUsageStore, }; + const versionServiceStores = { settingStore }; const getActiveUsers = createGetActiveUsers(db); const getProductionChanges = createGetProductionChanges(db); const getLicencedUsers = createGetLicensedUsers(db); - const versionService = new VersionService( - versionServiceStores, - config, - getActiveUsers, - getProductionChanges, - ); + const versionService = new VersionService(versionServiceStores, config); const instanceStatsService = new InstanceStatsService( instanceStatsServiceStores, @@ -170,7 +163,7 @@ export const createFakeInstanceStatsService = (config: IUnleashConfig) => { const clientMetricsStoreV2 = new FakeClientMetricsStoreV2(); const featureStrategiesReadModel = new FakeFeatureStrategiesReadModel(); const trafficDataUsageStore = new FakeTrafficDataUsageStore(); - + const featureStrategiesStore = new FakeFeatureStrategiesStore(); const instanceStatsServiceStores = { featureToggleStore, userStore, @@ -187,22 +180,15 @@ export const createFakeInstanceStatsService = (config: IUnleashConfig) => { apiTokenStore, clientMetricsStoreV2, featureStrategiesReadModel, - trafficDataUsageStore, - }; - const featureStrategiesStore = new FakeFeatureStrategiesStore(); - const versionServiceStores = { - ...instanceStatsServiceStores, featureStrategiesStore, + trafficDataUsageStore, }; + + const versionServiceStores = { settingStore }; const getActiveUsers = createFakeGetActiveUsers(); const getLicensedUsers = createFakeGetLicensedUsers(); const getProductionChanges = createFakeGetProductionChanges(); - const versionService = new VersionService( - versionServiceStores, - config, - getActiveUsers, - getProductionChanges, - ); + const versionService = new VersionService(versionServiceStores, config); const instanceStatsService = new InstanceStatsService( instanceStatsServiceStores, diff --git a/src/lib/features/instance-stats/instance-stats-service.test.ts b/src/lib/features/instance-stats/instance-stats-service.test.ts index 43fffce9eca1..fc878cb65327 100644 --- a/src/lib/features/instance-stats/instance-stats-service.test.ts +++ b/src/lib/features/instance-stats/instance-stats-service.test.ts @@ -27,12 +27,7 @@ beforeEach(() => { const config = createTestConfig(); flagResolver = config.flagResolver; stores = createStores(); - versionService = new VersionService( - stores, - config, - createFakeGetActiveUsers(), - createFakeGetProductionChanges(), - ); + versionService = new VersionService(stores, config); clientInstanceStore = stores.clientInstanceStore; instanceStatsService = new InstanceStatsService( stores, diff --git a/src/lib/features/instance-stats/instance-stats-service.ts b/src/lib/features/instance-stats/instance-stats-service.ts index dc7f5223dc1b..2349ce9281fd 100644 --- a/src/lib/features/instance-stats/instance-stats-service.ts +++ b/src/lib/features/instance-stats/instance-stats-service.ts @@ -6,6 +6,7 @@ import type { IClientMetricsStoreV2, IEventStore, IFeatureStrategiesReadModel, + IFeatureStrategiesStore, ITrafficDataUsageStore, IUnleashStores, } from '../../types/stores'; @@ -33,6 +34,7 @@ import type { GetProductionChanges } from './getProductionChanges'; import { format, minutesToMilliseconds } from 'date-fns'; import memoizee from 'memoizee'; import type { GetLicensedUsers } from './getLicensedUsers'; +import type { IFeatureUsageInfo } from '../../services/version-service'; export type TimeRange = 'allTime' | '30d' | '7d'; @@ -124,6 +126,8 @@ export class InstanceStatsService { private featureStrategiesReadModel: IFeatureStrategiesReadModel; + private featureStrategiesStore: IFeatureStrategiesStore; + private trafficDataUsageStore: ITrafficDataUsageStore; constructor( @@ -143,6 +147,7 @@ export class InstanceStatsService { apiTokenStore, clientMetricsStoreV2, featureStrategiesReadModel, + featureStrategiesStore, trafficDataUsageStore, }: Pick< IUnleashStores, @@ -161,6 +166,7 @@ export class InstanceStatsService { | 'apiTokenStore' | 'clientMetricsStoreV2' | 'featureStrategiesReadModel' + | 'featureStrategiesStore' | 'trafficDataUsageStore' >, { @@ -186,13 +192,20 @@ export class InstanceStatsService { this.eventStore = eventStore; this.clientInstanceStore = clientInstanceStore; this.logger = getLogger('services/stats-service.js'); - this.getActiveUsers = getActiveUsers; - this.getLicencedUsers = getLicencedUsers; - this.getProductionChanges = getProductionChanges; + this.getActiveUsers = () => + this.memorize('getActiveUsers', getActiveUsers.bind(this)); + this.getLicencedUsers = () => + this.memorize('getLicencedUsers', getLicencedUsers.bind(this)); + this.getProductionChanges = () => + this.memorize( + 'getProductionChanges', + getProductionChanges.bind(this), + ); this.apiTokenStore = apiTokenStore; this.clientMetricsStore = clientMetricsStoreV2; this.flagResolver = flagResolver; this.featureStrategiesReadModel = featureStrategiesReadModel; + this.featureStrategiesStore = featureStrategiesStore; this.trafficDataUsageStore = trafficDataUsageStore; } @@ -323,8 +336,8 @@ export class InstanceStatsService { this.getRegisteredUsers(), this.countServiceAccounts(), this.countApiTokensByType(), - this.memorize('getActiveUsers', this.getActiveUsers.bind(this)), - this.memorize('getLicencedUsers', this.getLicencedUsers.bind(this)), + this.getActiveUsers(), + this.getLicencedUsers(), this.getProjectModeCount(), this.contextFieldCount(), this.groupCount(), @@ -339,20 +352,9 @@ export class InstanceStatsService { this.hasPasswordAuth(), this.hasSCIM(), this.appCount ? this.appCount : this.getLabeledAppCounts(), - this.memorize('deprecatedFilteredCountFeaturesExported', () => - this.eventStore.deprecatedFilteredCount({ - type: FEATURES_EXPORTED, - }), - ), - this.memorize('deprecatedFilteredCountFeaturesImported', () => - this.eventStore.deprecatedFilteredCount({ - type: FEATURES_IMPORTED, - }), - ), - this.memorize( - 'getProductionChanges', - this.getProductionChanges.bind(this), - ), + this.featuresExported(), + this.featuresImported(), + this.getProductionChanges(), this.countPreviousDayHourlyMetricsBuckets(), this.memorize( 'maxFeatureEnvironmentStrategies', @@ -413,6 +415,124 @@ export class InstanceStatsService { }; } + async getFeatureUsageInfo(): Promise { + const [ + featureToggles, + users, + projectModeCount, + contextFields, + groups, + roles, + customRootRoles, + customRootRolesInUse, + environments, + segments, + strategies, + SAMLenabled, + OIDCenabled, + featureExports, + featureImports, + customStrategies, + customStrategiesInUse, + userActive, + productionChanges, + postgresVersion, + ] = await Promise.all([ + this.getToggleCount(), + this.getRegisteredUsers(), + this.getProjectModeCount(), + this.contextFieldCount(), + this.groupCount(), + this.roleCount(), + this.customRolesCount(), + this.customRolesCountInUse(), + this.environmentCount(), + this.segmentCount(), + this.strategiesCount(), + this.hasSAML(), + this.hasOIDC(), + this.featuresExported(), + this.featuresImported(), + this.customStrategiesCount(), + this.customStrategiesInUseCount(), + this.getActiveUsers(), + this.getProductionChanges(), + this.postgresVersion(), + ]); + const versionInfo = await this.versionService.getVersionInfo(); + + const featureInfo = { + featureToggles, + users, + projects: projectModeCount + .map((p) => p.count) + .reduce((a, b) => a + b, 0), + contextFields, + groups, + roles, + customRootRoles, + customRootRolesInUse, + environments, + segments, + strategies, + SAMLenabled, + OIDCenabled, + featureExports, + featureImports, + customStrategies, + customStrategiesInUse, + instanceId: versionInfo.instanceId, + versionOSS: versionInfo.current.oss, + versionEnterprise: versionInfo.current.enterprise, + activeUsers30: userActive.last30, + activeUsers60: userActive.last60, + activeUsers90: userActive.last90, + productionChanges30: productionChanges.last30, + productionChanges60: productionChanges.last60, + productionChanges90: productionChanges.last90, + postgresVersion, + }; + return featureInfo; + } + + featuresExported(): Promise { + return this.memorize('deprecatedFilteredCountFeaturesExported', () => + this.eventStore.deprecatedFilteredCount({ + type: FEATURES_EXPORTED, + }), + ); + } + + featuresImported(): Promise { + return this.memorize('deprecatedFilteredCountFeaturesImported', () => + this.eventStore.deprecatedFilteredCount({ + type: FEATURES_IMPORTED, + }), + ); + } + + customStrategiesCount(): Promise { + return this.memorize( + 'customStrategiesCount', + async () => + (await this.strategyStore.getEditableStrategies()).length, + ); + } + + customStrategiesInUseCount(): Promise { + return this.memorize( + 'customStrategiesInUseCount', + async () => + await this.featureStrategiesStore.getCustomStrategiesInUseCount(), + ); + } + + postgresVersion(): Promise { + return this.memorize('postgresVersion', () => + this.settingStore.postgresVersion(), + ); + } + groupCount(): Promise { return this.memorize('groupCount', () => this.groupStore.count()); } diff --git a/src/lib/features/scheduler/schedule-services.ts b/src/lib/features/scheduler/schedule-services.ts index 69d3a218fc1e..ad5a93bbe7ac 100644 --- a/src/lib/features/scheduler/schedule-services.ts +++ b/src/lib/features/scheduler/schedule-services.ts @@ -126,7 +126,10 @@ export const scheduleServices = async ( ); schedulerService.schedule( - versionService.checkLatestVersion.bind(versionService), + () => + versionService.checkLatestVersion(() => + instanceStatsService.getFeatureUsageInfo(), + ), hoursToMilliseconds(48), 'checkLatestVersion', ); diff --git a/src/lib/metrics.test.ts b/src/lib/metrics.test.ts index 4206024281be..d72b6cc61f2f 100644 --- a/src/lib/metrics.test.ts +++ b/src/lib/metrics.test.ts @@ -63,12 +63,7 @@ beforeAll(async () => { eventStore = stores.eventStore; environmentStore = new FakeEnvironmentStore(); stores.environmentStore = environmentStore; - const versionService = new VersionService( - stores, - config, - createFakeGetActiveUsers(), - createFakeGetProductionChanges(), - ); + const versionService = new VersionService(stores, config); db = await dbInit('metrics_test', getLogger); featureLifeCycleReadModel = new FeatureLifecycleReadModel( diff --git a/src/lib/services/index.ts b/src/lib/services/index.ts index 3015a22f76c5..f5a921858c54 100644 --- a/src/lib/services/index.ts +++ b/src/lib/services/index.ts @@ -82,10 +82,6 @@ import { createFakePrivateProjectChecker, createPrivateProjectChecker, } from '../features/private-project/createPrivateProjectChecker'; -import { - createFakeGetActiveUsers, - createGetActiveUsers, -} from '../features/instance-stats/getActiveUsers'; import { DependentFeaturesService } from '../features/dependent-features/dependent-features-service'; import { createDependentFeaturesService, @@ -97,10 +93,6 @@ import { createFakeLastSeenService, createLastSeenService, } from '../features/metrics/last-seen/createLastSeenService'; -import { - createFakeGetProductionChanges, - createGetProductionChanges, -} from '../features/instance-stats/getProductionChanges'; import { createClientFeatureToggleService, createFakeClientFeatureToggleService, @@ -247,19 +239,8 @@ export const createServices = ( const accountService = new AccountService(stores, config, { accessService, }); - const getActiveUsers = db - ? createGetActiveUsers(db) - : createFakeGetActiveUsers(); - const getProductionChanges = db - ? createGetProductionChanges(db) - : createFakeGetProductionChanges(); - - const versionService = new VersionService( - stores, - config, - getActiveUsers, - getProductionChanges, - ); + + const versionService = new VersionService(stores, config); const healthService = new HealthService(stores, config); const userFeedbackService = new UserFeedbackService(stores, config); const changeRequestAccessReadModel = db diff --git a/src/lib/services/version-service.test.ts b/src/lib/services/version-service.test.ts index 57371ba279b8..354005a62386 100644 --- a/src/lib/services/version-service.test.ts +++ b/src/lib/services/version-service.test.ts @@ -3,10 +3,7 @@ import createStores from '../../test/fixtures/store'; import version from '../util/version'; import getLogger from '../../test/fixtures/no-logger'; import VersionService from './version-service'; -import { v4 as uuidv4 } from 'uuid'; import { randomId } from '../util/random-id'; -import { createFakeGetActiveUsers } from '../features/instance-stats/getActiveUsers'; -import { createFakeGetProductionChanges } from '../features/instance-stats/getProductionChanges'; beforeAll(() => { nock.disableNetConnect(); @@ -16,10 +13,39 @@ afterAll(() => { nock.enableNetConnect(); }); +const fakeTelemetryData = { + featureToggles: 0, + users: 0, + projects: 1, + contextFields: 3, + groups: 0, + roles: 5, + customRootRoles: 0, + customRootRolesInUse: 0, + environments: 1, + segments: 0, + strategies: 3, + SAMLenabled: false, + OIDCenabled: false, + featureExports: 0, + featureImports: 0, + customStrategies: 3, + customStrategiesInUse: 0, + instanceId: '1460588e-d5f4-4ac2-9962-c8631f6b8dad', + versionOSS: '6.4.1', + versionEnterprise: '', + activeUsers30: 0, + activeUsers60: 0, + activeUsers90: 0, + productionChanges30: 0, + productionChanges60: 0, + productionChanges90: 0, + postgresVersion: '17.1 (Debian 17.1-1.pgdg120+1)', +}; + test('yields current versions', async () => { const url = `https://${randomId()}.example.com`; const stores = createStores(); - await stores.settingStore.insert('instanceInfo', { id: '1234abc' }); const latest = { oss: '5.0.0', enterprise: '5.0.0', @@ -33,17 +59,12 @@ test('yields current versions', async () => { versions: latest, }), ]); - const service = new VersionService( - stores, - { - getLogger, - versionCheck: { url, enable: true }, - telemetry: true, - }, - createFakeGetActiveUsers(), - createFakeGetProductionChanges(), - ); - await service.checkLatestVersion(); + const service = new VersionService(stores, { + getLogger, + versionCheck: { url, enable: true }, + telemetry: true, + }); + await service.checkLatestVersion(() => Promise.resolve(fakeTelemetryData)); const versionInfo = await service.getVersionInfo(); expect(scope.isDone()).toEqual(true); expect(versionInfo.current.oss).toBe(version); @@ -56,7 +77,6 @@ test('supports setting enterprise version as well', async () => { const url = `https://${randomId()}.example.com`; const stores = createStores(); const enterpriseVersion = '3.7.0'; - await stores.settingStore.insert('instanceInfo', { id: '1234abc' }); const latest = { oss: '4.0.0', enterprise: '4.0.0', @@ -71,18 +91,13 @@ test('supports setting enterprise version as well', async () => { }), ]); - const service = new VersionService( - stores, - { - getLogger, - versionCheck: { url, enable: true }, - enterpriseVersion, - telemetry: true, - }, - createFakeGetActiveUsers(), - createFakeGetProductionChanges(), - ); - await service.checkLatestVersion(); + const service = new VersionService(stores, { + getLogger, + versionCheck: { url, enable: true }, + enterpriseVersion, + telemetry: true, + }); + await service.checkLatestVersion(() => Promise.resolve(fakeTelemetryData)); const versionInfo = await service.getVersionInfo(); expect(scope.isDone()).toEqual(true); expect(versionInfo.current.oss).toBe(version); @@ -95,7 +110,6 @@ test('if version check is not enabled should not make any calls', async () => { const url = `https://${randomId()}.example.com`; const stores = createStores(); const enterpriseVersion = '3.7.0'; - await stores.settingStore.insert('instanceInfo', { id: '1234abc' }); const latest = { oss: '4.0.0', enterprise: '4.0.0', @@ -110,18 +124,13 @@ test('if version check is not enabled should not make any calls', async () => { }), ]); - const service = new VersionService( - stores, - { - getLogger, - versionCheck: { url, enable: false }, - enterpriseVersion, - telemetry: true, - }, - createFakeGetActiveUsers(), - createFakeGetProductionChanges(), - ); - await service.checkLatestVersion(); + const service = new VersionService(stores, { + getLogger, + versionCheck: { url, enable: false }, + enterpriseVersion, + telemetry: true, + }); + await service.checkLatestVersion(() => Promise.resolve(fakeTelemetryData)); const versionInfo = await service.getVersionInfo(); expect(scope.isDone()).toEqual(false); expect(versionInfo.current.oss).toBe(version); @@ -135,7 +144,6 @@ test('sets featureinfo', async () => { const url = `https://${randomId()}.example.com`; const stores = createStores(); const enterpriseVersion = '4.0.0'; - await stores.settingStore.insert('instanceInfo', { id: '1234abc' }); const latest = { oss: '4.0.0', enterprise: '4.0.0', @@ -146,8 +154,10 @@ test('sets featureinfo', async () => { '/', (body) => body.featureInfo && - body.featureInfo.featureToggles === 0 && - body.featureInfo.environments === 0, + body.featureInfo.featureToggles === + fakeTelemetryData.featureToggles && + body.featureInfo.environments === + fakeTelemetryData.environments, ) .reply(() => [ 200, @@ -157,18 +167,13 @@ test('sets featureinfo', async () => { }), ]); - const service = new VersionService( - stores, - { - getLogger, - versionCheck: { url, enable: true }, - enterpriseVersion, - telemetry: true, - }, - createFakeGetActiveUsers(), - createFakeGetProductionChanges(), - ); - await service.checkLatestVersion(); + const service = new VersionService(stores, { + getLogger, + versionCheck: { url, enable: true }, + enterpriseVersion, + telemetry: true, + }); + await service.checkLatestVersion(() => Promise.resolve(fakeTelemetryData)); expect(scope.isDone()).toEqual(true); nock.cleanAll(); }); @@ -177,19 +182,6 @@ test('counts toggles', async () => { const url = `https://${randomId()}.example.com`; const stores = createStores(); const enterpriseVersion = '4.0.0'; - await stores.settingStore.insert('instanceInfo', { id: '1234abc' }); - await stores.settingStore.insert('unleash.enterprise.auth.oidc', { - enabled: true, - }); - await stores.featureToggleStore.create('project', { - name: uuidv4(), - createdByUserId: 9999, - }); - await stores.strategyStore.createStrategy({ - name: uuidv4(), - editable: true, - parameters: [], - }); const latest = { oss: '4.0.0', enterprise: '4.0.0', @@ -200,11 +192,15 @@ test('counts toggles', async () => { '/', (body) => body.featureInfo && - body.featureInfo.featureToggles === 1 && - body.featureInfo.environments === 0 && - body.featureInfo.customStrategies === 1 && - body.featureInfo.customStrategiesInUse === 3 && - body.featureInfo.OIDCenabled, + body.featureInfo.featureToggles === + fakeTelemetryData.featureToggles && + body.featureInfo.environments === + fakeTelemetryData.environments && + body.featureInfo.customStrategies === + fakeTelemetryData.customStrategies && + body.featureInfo.customStrategiesInUse === + fakeTelemetryData.customRootRolesInUse && + body.featureInfo.OIDCenabled === fakeTelemetryData.OIDCenabled, ) .reply(() => [ 200, @@ -214,18 +210,13 @@ test('counts toggles', async () => { }), ]); - const service = new VersionService( - stores, - { - getLogger, - versionCheck: { url, enable: true }, - enterpriseVersion, - telemetry: true, - }, - createFakeGetActiveUsers(), - createFakeGetProductionChanges(), - ); - await service.checkLatestVersion(); + const service = new VersionService(stores, { + getLogger, + versionCheck: { url, enable: true }, + enterpriseVersion, + telemetry: true, + }); + await service.checkLatestVersion(() => Promise.resolve(fakeTelemetryData)); expect(scope.isDone()).toEqual(true); nock.cleanAll(); }); @@ -234,34 +225,7 @@ test('counts custom strategies', async () => { const url = `https://${randomId()}.example.com`; const stores = createStores(); const enterpriseVersion = '4.0.0'; - const strategyName = uuidv4(); - const toggleName = uuidv4(); - await stores.settingStore.insert('instanceInfo', { id: '1234abc' }); - await stores.settingStore.insert('unleash.enterprise.auth.oidc', { - enabled: true, - }); - await stores.featureToggleStore.create('project', { - name: toggleName, - createdByUserId: 9999, - }); - await stores.strategyStore.createStrategy({ - name: strategyName, - editable: true, - parameters: [], - }); - await stores.strategyStore.createStrategy({ - name: uuidv4(), - editable: true, - parameters: [], - }); - await stores.featureStrategiesStore.createStrategyFeatureEnv({ - featureName: toggleName, - projectId: 'project', - environment: 'default', - strategyName: strategyName, - parameters: {}, - constraints: [], - }); + const latest = { oss: '4.0.0', enterprise: '4.0.0', @@ -272,11 +236,15 @@ test('counts custom strategies', async () => { '/', (body) => body.featureInfo && - body.featureInfo.featureToggles === 1 && - body.featureInfo.environments === 0 && - body.featureInfo.customStrategies === 2 && - body.featureInfo.customStrategiesInUse === 3 && - body.featureInfo.OIDCenabled, + body.featureInfo.featureToggles === + fakeTelemetryData.featureToggles && + body.featureInfo.environments === + fakeTelemetryData.environments && + body.featureInfo.customStrategies === + fakeTelemetryData.customStrategies && + body.featureInfo.customStrategiesInUse === + fakeTelemetryData.customRootRolesInUse && + body.featureInfo.OIDCenabled === fakeTelemetryData.OIDCenabled, ) .reply(() => [ 200, @@ -286,18 +254,13 @@ test('counts custom strategies', async () => { }), ]); - const service = new VersionService( - stores, - { - getLogger, - versionCheck: { url, enable: true }, - enterpriseVersion, - telemetry: true, - }, - createFakeGetActiveUsers(), - createFakeGetProductionChanges(), - ); - await service.checkLatestVersion(); + const service = new VersionService(stores, { + getLogger, + versionCheck: { url, enable: true }, + enterpriseVersion, + telemetry: true, + }); + await service.checkLatestVersion(() => Promise.resolve(fakeTelemetryData)); expect(scope.isDone()).toEqual(true); nock.cleanAll(); }); @@ -306,26 +269,22 @@ test('counts active users', async () => { const url = `https://${randomId()}.example.com`; const stores = createStores(); const enterpriseVersion = '4.0.0'; - await stores.settingStore.insert('instanceInfo', { id: '1234abc' }); const latest = { oss: '4.0.0', enterprise: '4.0.0', }; - const fakeActiveUsers = createFakeGetActiveUsers({ - last7: 2, - last30: 5, - last60: 10, - last90: 20, - }); - const fakeProductionChanges = createFakeGetProductionChanges(); + const scope = nock(url) .post( '/', (body) => body.featureInfo && - body.featureInfo.activeUsers30 === 5 && - body.featureInfo.activeUsers60 === 10 && - body.featureInfo.activeUsers90 === 20, + body.featureInfo.activeUsers30 === + fakeTelemetryData.activeUsers30 && + body.featureInfo.activeUsers60 === + fakeTelemetryData.activeUsers60 && + body.featureInfo.activeUsers90 === + fakeTelemetryData.activeUsers90, ) .reply(() => [ 200, @@ -335,18 +294,13 @@ test('counts active users', async () => { }), ]); - const service = new VersionService( - stores, - { - getLogger, - versionCheck: { url, enable: true }, - enterpriseVersion, - telemetry: true, - }, - fakeActiveUsers, - fakeProductionChanges, - ); - await service.checkLatestVersion(); + const service = new VersionService(stores, { + getLogger, + versionCheck: { url, enable: true }, + enterpriseVersion, + telemetry: true, + }); + await service.checkLatestVersion(() => Promise.resolve(fakeTelemetryData)); expect(scope.isDone()).toEqual(true); nock.cleanAll(); }); @@ -354,25 +308,21 @@ test('Counts production changes', async () => { const url = `https://${randomId()}.example.com`; const stores = createStores(); const enterpriseVersion = '4.0.0'; - await stores.settingStore.insert('instanceInfo', { id: '1234abc' }); const latest = { oss: '4.0.0', enterprise: '4.0.0', }; - const fakeActiveUsers = createFakeGetActiveUsers(); - const fakeProductionChanges = createFakeGetProductionChanges({ - last30: 5, - last60: 10, - last90: 20, - }); const scope = nock(url) .post( '/', (body) => body.featureInfo && - body.featureInfo.productionChanges30 === 5 && - body.featureInfo.productionChanges60 === 10 && - body.featureInfo.productionChanges90 === 20, + body.featureInfo.productionChanges30 === + fakeTelemetryData.productionChanges30 && + body.featureInfo.productionChanges60 === + fakeTelemetryData.productionChanges60 && + body.featureInfo.productionChanges90 === + fakeTelemetryData.productionChanges90, ) .reply(() => [ 200, @@ -382,18 +332,13 @@ test('Counts production changes', async () => { }), ]); - const service = new VersionService( - stores, - { - getLogger, - versionCheck: { url, enable: true }, - enterpriseVersion, - telemetry: true, - }, - fakeActiveUsers, - fakeProductionChanges, - ); - await service.checkLatestVersion(); + const service = new VersionService(stores, { + getLogger, + versionCheck: { url, enable: true }, + enterpriseVersion, + telemetry: true, + }); + await service.checkLatestVersion(() => Promise.resolve(fakeTelemetryData)); expect(scope.isDone()).toEqual(true); nock.cleanAll(); }); diff --git a/src/lib/services/version-service.ts b/src/lib/services/version-service.ts index 4e2648e638e3..798e9406a23d 100644 --- a/src/lib/services/version-service.ts +++ b/src/lib/services/version-service.ts @@ -1,26 +1,9 @@ import fetch from 'make-fetch-happen'; -import type { - IContextFieldStore, - IEnvironmentStore, - IEventStore, - IFeatureStrategiesStore, - IFeatureToggleStore, - IGroupStore, - IProjectStore, - IRoleStore, - ISegmentStore, - IUnleashStores, - IUserStore, -} from '../types/stores'; +import type { IUnleashStores } from '../types/stores'; import type { IUnleashConfig } from '../types/option'; import version from '../util/version'; import type { Logger } from '../logger'; import type { ISettingStore } from '../types/stores/settings-store'; -import type { IStrategyStore } from '../types'; -import { FEATURES_EXPORTED, FEATURES_IMPORTED } from '../types'; -import { CUSTOM_ROOT_ROLE_TYPE } from '../util'; -import type { GetActiveUsers } from '../features/instance-stats/getActiveUsers'; -import type { GetProductionChanges } from '../features/instance-stats/getProductionChanges'; export interface IVersionInfo { oss: string; @@ -73,32 +56,6 @@ export default class VersionService { private settingStore: ISettingStore; - private strategyStore: IStrategyStore; - - private userStore: IUserStore; - - private featureToggleStore: IFeatureToggleStore; - - private projectStore: IProjectStore; - - private environmentStore: IEnvironmentStore; - - private contextFieldStore: IContextFieldStore; - - private groupStore: IGroupStore; - - private roleStore: IRoleStore; - - private segmentStore: ISegmentStore; - - private eventStore: IEventStore; - - private featureStrategiesStore: IFeatureStrategiesStore; - - private getActiveUsers: GetActiveUsers; - - private getProductionChanges: GetProductionChanges; - private current: IVersionInfo; private latest?: IVersionInfo; @@ -116,34 +73,7 @@ export default class VersionService { private timer: NodeJS.Timeout; constructor( - { - settingStore, - strategyStore, - userStore, - featureToggleStore, - projectStore, - environmentStore, - contextFieldStore, - groupStore, - roleStore, - segmentStore, - eventStore, - featureStrategiesStore, - }: Pick< - IUnleashStores, - | 'settingStore' - | 'strategyStore' - | 'userStore' - | 'featureToggleStore' - | 'projectStore' - | 'environmentStore' - | 'contextFieldStore' - | 'groupStore' - | 'roleStore' - | 'segmentStore' - | 'eventStore' - | 'featureStrategiesStore' - >, + { settingStore }: Pick, { getLogger, versionCheck, @@ -153,24 +83,9 @@ export default class VersionService { IUnleashConfig, 'getLogger' | 'versionCheck' | 'enterpriseVersion' | 'telemetry' >, - getActiveUsers: GetActiveUsers, - getProductionChanges: GetProductionChanges, ) { this.logger = getLogger('lib/services/version-service.js'); this.settingStore = settingStore; - this.strategyStore = strategyStore; - this.userStore = userStore; - this.featureToggleStore = featureToggleStore; - this.projectStore = projectStore; - this.environmentStore = environmentStore; - this.contextFieldStore = contextFieldStore; - this.groupStore = groupStore; - this.roleStore = roleStore; - this.segmentStore = segmentStore; - this.eventStore = eventStore; - this.getActiveUsers = getActiveUsers; - this.getProductionChanges = getProductionChanges; - this.featureStrategiesStore = featureStrategiesStore; this.current = { oss: version, enterprise: enterpriseVersion || '', @@ -199,7 +114,9 @@ export default class VersionService { return this.instanceId; } - async checkLatestVersion(): Promise { + async checkLatestVersion( + telemetryDataProvider: () => Promise, + ): Promise { const instanceId = await this.getInstanceId(); this.logger.debug( `Checking for newest version for instanceId=${instanceId}`, @@ -212,8 +129,7 @@ export default class VersionService { }; if (this.telemetryEnabled) { - versionPayload.featureInfo = - await this.getFeatureUsageInfo(); + versionPayload.featureInfo = await telemetryDataProvider(); } if (this.versionCheckUrl) { const res = await fetch(this.versionCheckUrl, { @@ -241,129 +157,6 @@ export default class VersionService { } } } - - async getFeatureUsageInfo(): Promise { - const [ - featureToggles, - users, - projects, - contextFields, - groups, - roles, - customRootRoles, - customRootRolesInUse, - environments, - segments, - strategies, - SAMLenabled, - OIDCenabled, - featureExports, - featureImports, - userActive, - productionChanges, - postgresVersion, - ] = await Promise.all([ - this.featureToggleStore.count({ - archived: false, - }), - this.userStore.count(), - this.projectStore.count(), - this.contextFieldStore.count(), - this.groupStore.count(), - this.roleStore.count(), - this.roleStore.filteredCount({ - type: CUSTOM_ROOT_ROLE_TYPE, - }), - this.roleStore.filteredCountInUse({ type: CUSTOM_ROOT_ROLE_TYPE }), - this.environmentStore.count(), - this.segmentStore.count(), - this.strategyStore.count(), - this.hasSAML(), - this.hasOIDC(), - this.eventStore.deprecatedFilteredCount({ - type: FEATURES_EXPORTED, - }), - this.eventStore.deprecatedFilteredCount({ - type: FEATURES_IMPORTED, - }), - this.userStats(), - this.productionChanges(), - this.postgresVersion(), - ]); - const versionInfo = await this.getVersionInfo(); - const customStrategies = - await this.strategyStore.getEditableStrategies(); - const customStrategiesInUse = - await this.featureStrategiesStore.getCustomStrategiesInUseCount(); - const featureInfo = { - featureToggles, - users, - projects, - contextFields, - groups, - roles, - customRootRoles, - customRootRolesInUse, - environments, - segments, - strategies, - SAMLenabled, - OIDCenabled, - featureExports, - featureImports, - customStrategies: customStrategies.length, - customStrategiesInUse: customStrategiesInUse, - instanceId: versionInfo.instanceId, - versionOSS: versionInfo.current.oss, - versionEnterprise: versionInfo.current.enterprise, - activeUsers30: userActive.last30, - activeUsers60: userActive.last60, - activeUsers90: userActive.last90, - productionChanges30: productionChanges.last30, - productionChanges60: productionChanges.last60, - productionChanges90: productionChanges.last90, - postgresVersion, - }; - return featureInfo; - } - - async userStats(): Promise<{ - last30: number; - last60: number; - last90: number; - }> { - const { last30, last60, last90 } = await this.getActiveUsers(); - return { last30, last60, last90 }; - } - - async productionChanges(): Promise<{ - last30: number; - last60: number; - last90: number; - }> { - return this.getProductionChanges(); - } - - async postgresVersion(): Promise { - return this.settingStore.postgresVersion(); - } - - async hasOIDC(): Promise { - const settings = await this.settingStore.get<{ enabled: boolean }>( - 'unleash.enterprise.auth.oidc', - ); - - return settings?.enabled || false; - } - - async hasSAML(): Promise { - const settings = await this.settingStore.get<{ enabled: boolean }>( - 'unleash.enterprise.auth.saml', - ); - - return settings?.enabled || false; - } - async getVersionInfo(): Promise { const instanceId = await this.getInstanceId(); return {