UNPKG

unleash-server

Version:

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

143 lines 6.38 kB
import { ALL } from '../../types/models/api-token.js'; import { offlineUnleashClient } from './offline-unleash-client.js'; import { generateObjectCombinations } from './generateObjectCombinations.js'; import groupBy from 'lodash.groupby'; import { omitKeys } from '../../util/index.js'; import { validateQueryComplexity } from './validateQueryComplexity.js'; import { getDefaultVariant } from './feature-evaluator/variant.js'; import { cleanContext } from './clean-context.js'; export class PlaygroundService { constructor(config, { featureToggleService, privateProjectChecker, }, segmentReadModel) { this.logger = config.getLogger('services/playground-service.ts'); this.flagResolver = config.flagResolver; this.featureToggleService = featureToggleService; this.privateProjectChecker = privateProjectChecker; this.segmentReadModel = segmentReadModel; } async evaluateAdvancedQuery(projects, environments, context, userId) { // used for runtime control, do not remove const { payload } = this.flagResolver.getVariant('advancedPlayground'); const limit = payload?.value && Number.isInteger(Number.parseInt(payload?.value)) ? Number.parseInt(payload?.value) : 15000; const segments = await this.segmentReadModel.getActive(); let filteredProjects = projects; const projectAccess = await this.privateProjectChecker.getUserAccessibleProjects(userId); if (projectAccess.mode === 'all') { filteredProjects = projects; } else if (projects === ALL) { filteredProjects = projectAccess.projects; } else { filteredProjects = projects.filter((project) => projectAccess.projects.includes(project)); } const environmentFeatures = await Promise.all(environments.map((env) => this.resolveFeatures(filteredProjects, env))); const { context: cleanedContext, removedProperties } = cleanContext(context); const contexts = generateObjectCombinations(cleanedContext); validateQueryComplexity(environments.length, environmentFeatures[0]?.features.length ?? 0, contexts.length, limit); const results = await Promise.all(environmentFeatures.flatMap(({ features, featureProject, environment }) => contexts.map((singleContext) => this.evaluate({ features, featureProject, context: singleContext, segments, environment, })))); const items = results.flat(); const itemsByName = groupBy(items, (item) => item.name); const result = Object.values(itemsByName).map((entries) => { const groupedEnvironments = groupBy(entries, (entry) => entry.environment); return { name: entries[0].name, projectId: entries[0].projectId, environments: groupedEnvironments, }; }); return { result, invalidContextProperties: removedProperties, }; } async evaluate({ featureProject, features, segments, context, environment, }) { const [head, ...rest] = features; if (!head) { return []; } else { const client = await offlineUnleashClient({ features: [head, ...rest], context, logError: this.logger.error, segments, }); const variantsMap = features.reduce((acc, feature) => { acc[feature.name] = feature.variants; return acc; }, {}); const clientContext = { ...context, currentTime: context.currentTime ? new Date(context.currentTime) : undefined, }; return client .getFeatureToggleDefinitions() .map((feature) => { const strategyEvaluationResult = client.isEnabled(feature.name, clientContext); const hasUnsatisfiedDependency = strategyEvaluationResult.hasUnsatisfiedDependency; const isEnabled = strategyEvaluationResult.result === true && feature.enabled && !hasUnsatisfiedDependency; const variant = { ...(isEnabled ? client.forceGetVariant(feature.name, strategyEvaluationResult, clientContext) : getDefaultVariant()), feature_enabled: isEnabled, }; return { isEnabled, isEnabledInCurrentEnvironment: feature.enabled, hasUnsatisfiedDependency, strategies: { result: strategyEvaluationResult.result, data: strategyEvaluationResult.strategies, }, projectId: featureProject[feature.name], variant, name: feature.name, environment, context, variants: strategyEvaluationResult.variants || variantsMap[feature.name] || [], }; }); } } async resolveFeatures(projects, environment) { const features = await this.featureToggleService.getPlaygroundFeatures({ project: projects === ALL ? undefined : projects, environment, }); const featureProject = features.reduce((obj, feature) => { obj[feature.name] = feature.project; return obj; }, {}); return { features, featureProject, environment }; } async evaluateQuery(projects, environment, context) { const [{ features, featureProject }, segments] = await Promise.all([ this.resolveFeatures(projects, environment), this.segmentReadModel.getActive(), ]); const result = await this.evaluate({ features, featureProject, segments, context, environment, }); return result.map((item) => omitKeys(item, 'environment', 'context')); } } //# sourceMappingURL=playground-service.js.map