UNPKG

unleash-server

Version:

Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.

329 lines • 14.2 kB
import { sha256 } from 'js-sha256'; import { FEATURES_EXPORTED, FEATURES_IMPORTED } from '../../events/index.js'; import { CUSTOM_ROOT_ROLE_TYPE } from '../../util/index.js'; import { format, minutesToMilliseconds } from 'date-fns'; import memoizee from 'memoizee'; export class InstanceStatsService { constructor({ featureToggleStore, userStore, projectStore, environmentStore, strategyStore, contextFieldStore, groupStore, segmentStore, roleStore, settingStore, clientInstanceStore, eventStore, apiTokenStore, clientMetricsStoreV2, featureStrategiesReadModel, featureStrategiesStore, trafficDataUsageStore, }, { getLogger, flagResolver, }, versionService, getActiveUsers, getProductionChanges, getLicencedUsers) { this.memory = new Map(); this.strategyStore = strategyStore; this.userStore = userStore; this.featureToggleStore = featureToggleStore; this.environmentStore = environmentStore; this.projectStore = projectStore; this.groupStore = groupStore; this.contextFieldStore = contextFieldStore; this.segmentStore = segmentStore; this.roleStore = roleStore; this.versionService = versionService; this.settingStore = settingStore; this.eventStore = eventStore; this.clientInstanceStore = clientInstanceStore; this.logger = getLogger('services/stats-service.js'); 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; } memorize(key, fn) { const variant = this.flagResolver.getVariant('memorizeStats', { memoryKey: key, }); if (variant.feature_enabled) { const minutes = variant.payload?.type === 'number' ? Number(variant.payload.value) : 1; let memoizedFunction = this.memory.get(key); if (!memoizedFunction) { memoizedFunction = memoizee(() => fn(), { promise: true, maxAge: minutesToMilliseconds(minutes), }); this.memory.set(key, memoizedFunction); } return memoizedFunction(); } else { return fn(); } } getProjectModeCount() { return this.memorize('getProjectModeCount', () => this.projectStore.getProjectModeCounts()); } getToggleCount() { return this.memorize('getToggleCount', () => this.featureToggleStore.count({ archived: false, })); } getArchivedToggleCount() { return this.memorize('hasOIDC', () => this.featureToggleStore.count({ archived: true, })); } async hasOIDC() { return this.memorize('hasOIDC', async () => { const settings = await this.settingStore.get('unleash.enterprise.auth.oidc'); return settings?.enabled || false; }); } async hasSAML() { return this.memorize('hasSAML', async () => { const settings = await this.settingStore.get('unleash.enterprise.auth.saml'); return settings?.enabled || false; }); } async hasPasswordAuth() { return this.memorize('hasPasswordAuth', async () => { const settings = await this.settingStore.get('unleash.auth.simple'); return (typeof settings?.disabled === 'undefined' || settings.disabled === false); }); } async hasSCIM() { return this.memorize('hasSCIM', async () => { const settings = await this.settingStore.get('scim'); return settings?.enabled || false; }); } async getStats() { const versionInfo = await this.versionService.getVersionInfo(); const [featureToggles, archivedFeatureToggles, users, serviceAccounts, apiTokens, activeUsers, licensedUsers, projects, contextFields, groups, roles, customRootRoles, customRootRolesInUse, environments, segments, strategies, SAMLenabled, OIDCenabled, passwordAuthEnabled, SCIMenabled, clientApps, featureExports, featureImports, productionChanges, previousDayMetricsBucketsCount, maxEnvironmentStrategies, maxConstraintValues, maxConstraints,] = await Promise.all([ this.getToggleCount(), this.getArchivedToggleCount(), this.getRegisteredUsers(), this.countServiceAccounts(), this.countApiTokensByType(), this.getActiveUsers(), this.getLicencedUsers(), this.getProjectModeCount(), this.contextFieldCount(), this.groupCount(), this.roleCount(), this.customRolesCount(), this.customRolesCountInUse(), this.environmentCount(), this.segmentCount(), this.strategiesCount(), this.hasSAML(), this.hasOIDC(), this.hasPasswordAuth(), this.hasSCIM(), this.appCount ? this.appCount : this.getLabeledAppCounts(), this.featuresExported(), this.featuresImported(), this.getProductionChanges(), this.countPreviousDayHourlyMetricsBuckets(), this.memorize('maxFeatureEnvironmentStrategies', this.featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies.bind(this.featureStrategiesReadModel)), this.memorize('maxConstraintValues', this.featureStrategiesReadModel.getMaxConstraintValues.bind(this.featureStrategiesReadModel)), this.memorize('maxConstraintsPerStrategy', this.featureStrategiesReadModel.getMaxConstraintsPerStrategy.bind(this.featureStrategiesReadModel)), ]); return { timestamp: new Date(), instanceId: versionInfo.instanceId, versionOSS: versionInfo.current.oss, versionEnterprise: versionInfo.current.enterprise, users, serviceAccounts, apiTokens, activeUsers, licensedUsers, featureToggles, archivedFeatureToggles, projects, contextFields, roles, customRootRoles, customRootRolesInUse, groups, environments, segments, strategies, SAMLenabled, OIDCenabled, passwordAuthEnabled, SCIMenabled, clientApps: Object.entries(clientApps).map(([range, count]) => ({ range: range, count, })), featureExports, featureImports, productionChanges, previousDayMetricsBucketsCount, maxEnvironmentStrategies: maxEnvironmentStrategies?.count ?? 0, maxConstraintValues: maxConstraintValues?.count ?? 0, maxConstraints: maxConstraints?.count ?? 0, }; } async getFeatureUsageInfo() { const [featureToggles, users, projectModeCount, contextFields, groups, roles, customRootRoles, customRootRolesInUse, environments, segments, strategies, SAMLenabled, OIDCenabled, featureExports, featureImports, customStrategies, customStrategiesInUse, userActive, productionChanges, postgresVersion, licenseType, hostedBy,] = 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(), this.getLicenseType(), this.getHostedBy(), ]); 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, licenseType, hostedBy, }; return featureInfo; } getHostedBy() { return 'self-hosted'; } getLicenseType() { return 'oss'; } featuresExported() { return this.memorize('searchEventsCountFeaturesExported', () => this.eventStore.searchEventsCount([ { field: 'type', operator: 'IS', values: [FEATURES_EXPORTED], }, ])); } featuresImported() { return this.memorize('searchEventsCountFeaturesImported', () => this.eventStore.searchEventsCount([ { field: 'type', operator: 'IS', values: [FEATURES_IMPORTED], }, ])); } customStrategiesCount() { return this.memorize('customStrategiesCount', async () => (await this.strategyStore.getEditableStrategies()).length); } customStrategiesInUseCount() { return this.memorize('customStrategiesInUseCount', async () => await this.featureStrategiesStore.getCustomStrategiesInUseCount()); } postgresVersion() { return this.memorize('postgresVersion', () => this.settingStore.postgresVersion()); } groupCount() { return this.memorize('groupCount', () => this.groupStore.count()); } roleCount() { return this.memorize('roleCount', () => this.roleStore.count()); } customRolesCount() { return this.memorize('customRolesCount', () => this.roleStore.filteredCount({ type: CUSTOM_ROOT_ROLE_TYPE })); } customRolesCountInUse() { return this.memorize('customRolesCountInUse', () => this.roleStore.filteredCountInUse({ type: CUSTOM_ROOT_ROLE_TYPE, })); } segmentCount() { return this.memorize('segmentCount', () => this.segmentStore.count()); } contextFieldCount() { return this.memorize('contextFieldCount', () => this.contextFieldStore.count()); } strategiesCount() { return this.memorize('strategiesCount', () => this.strategyStore.count()); } environmentCount() { return this.memorize('environmentCount', () => this.environmentStore.count()); } countPreviousDayHourlyMetricsBuckets() { return this.memorize('countPreviousDayHourlyMetricsBuckets', () => this.clientMetricsStore.countPreviousDayHourlyMetricsBuckets()); } countApiTokensByType() { return this.memorize('countApiTokensByType', () => this.apiTokenStore.countByType()); } getRegisteredUsers() { return this.memorize('getRegisteredUsers', () => this.userStore.count()); } countServiceAccounts() { return this.memorize('countServiceAccounts', () => this.userStore.countServiceAccounts()); } async getCurrentTrafficData() { return this.memorize('getCurrentTrafficData', async () => { const traffic = await this.trafficDataUsageStore.getTrafficDataUsageForPeriod(format(new Date(), 'yyyy-MM')); const counts = traffic.map((item) => item.count); return counts.reduce((total, current) => total + current, 0); }); } async getLabeledAppCounts() { return this.memorize('getLabeledAppCounts', async () => { const [t7d, t30d, allTime] = await Promise.all([ this.clientInstanceStore.getDistinctApplicationsCount(7), this.clientInstanceStore.getDistinctApplicationsCount(30), this.clientInstanceStore.getDistinctApplicationsCount(), ]); this.appCount = { '7d': t7d, '30d': t30d, allTime, }; return this.appCount; }); } getAppCountSnapshot(range) { return this.appCount?.[range]; } async getSignedStats() { const instanceStats = await this.getStats(); const totalProjects = instanceStats.projects .map((p) => p.count) .reduce((a, b) => a + b, 0); const sum = sha256(`${instanceStats.instanceId}${instanceStats.users}${instanceStats.featureToggles}${totalProjects}${instanceStats.roles}${instanceStats.groups}${instanceStats.environments}${instanceStats.segments}`); return { ...instanceStats, sum, projects: totalProjects }; } } //# sourceMappingURL=instance-stats-service.js.map