UNPKG

unleash-server

Version:

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

137 lines • 6.77 kB
import { DefaultStrategyUpdatedEvent, ProjectEnvironmentAdded, ProjectEnvironmentRemoved, SYSTEM_USER_AUDIT, } from '../../types/index.js'; import { BadDataError, UNIQUE_CONSTRAINT_VIOLATION, } from '../../error/index.js'; import NameExistsError from '../../error/name-exists-error.js'; import { sortOrderSchema } from '../../services/sort-order-schema.js'; import NotFoundError from '../../error/notfound-error.js'; export default class EnvironmentService { constructor({ environmentStore, featureStrategiesStore, featureEnvironmentStore, projectStore, }, { getLogger, flagResolver, }, eventService) { this.logger = getLogger('services/environment-service.ts'); this.environmentStore = environmentStore; this.featureStrategiesStore = featureStrategiesStore; this.featureEnvironmentStore = featureEnvironmentStore; this.projectStore = projectStore; this.eventService = eventService; this.flagResolver = flagResolver; } async getAll() { return this.environmentStore.getAllWithCounts(); } async get(name) { const env = await this.environmentStore.get(name); if (env === undefined) { throw new NotFoundError(`Could not find environment with name ${name}`); } return env; } async exists(name) { return this.environmentStore.exists(name); } async getProjectEnvironments(projectId) { // This function produces an object for every environment, in that object is a boolean // describing whether that environment is enabled - aka not deprecated const environments = await this.projectStore.getEnvironmentsForProject(projectId); const environmentsOnProject = new Set(environments.map((env) => env.environment)); const allEnvironments = await this.environmentStore.getProjectEnvironments(projectId); return allEnvironments.map((env) => { return { ...env, visible: environmentsOnProject.has(env.name), }; }); } async updateSortOrder(sortOrder) { await sortOrderSchema.validateAsync(sortOrder); await Promise.all(Object.keys(sortOrder).map((key) => { const value = sortOrder[key]; return this.environmentStore.updateSortOrder(key, value); })); } async toggleEnvironment(name, value) { const exists = await this.environmentStore.exists(name); if (exists) { return this.environmentStore.toggle(name, value); } throw new NotFoundError(`Could not find environment ${name}`); } async addEnvironmentToProject(environment, projectId, auditUser) { try { await this.featureEnvironmentStore.connectProject(environment, projectId); await this.featureEnvironmentStore.connectFeatures(environment, projectId); await this.eventService.storeEvent(new ProjectEnvironmentAdded({ project: projectId, environment, auditUser, })); } catch (e) { if (e.code === UNIQUE_CONSTRAINT_VIOLATION) { throw new NameExistsError(`${projectId} already has the environment ${environment} enabled`); } throw e; } } async updateDefaultStrategy(environment, projectId, strategy, auditUser) { if (strategy.name !== 'flexibleRollout') { throw new BadDataError('Only "flexibleRollout" strategy can be used as a default strategy for an environment'); } const previousDefaultStrategy = await this.projectStore.getDefaultStrategy(projectId, environment); const defaultStrategy = await this.projectStore.updateDefaultStrategy(projectId, environment, strategy); await this.eventService.storeEvent(new DefaultStrategyUpdatedEvent({ project: projectId, environment, preData: previousDefaultStrategy, data: defaultStrategy, auditUser, })); return defaultStrategy; } async overrideEnabledProjects(environmentNamesToEnable) { if (environmentNamesToEnable.length === 0) { return Promise.resolve(); } const allEnvironments = await this.environmentStore.getAll(); const existingEnvironmentsToEnable = allEnvironments.filter((env) => environmentNamesToEnable.includes(env.name)); if (existingEnvironmentsToEnable.length !== environmentNamesToEnable.length) { this.logger.warn("Found environment enabled overrides but some of the specified environments don't exist, no overrides will be executed"); return Promise.resolve(); } const environmentsNotAlreadyEnabled = existingEnvironmentsToEnable.filter((env) => !env.enabled); const environmentsToDisable = allEnvironments.filter((env) => { return !environmentNamesToEnable.includes(env.name) && env.enabled; }); await this.environmentStore.disable(environmentsToDisable); await this.environmentStore.enable(environmentsNotAlreadyEnabled); await this.remapProjectsLinks(environmentsToDisable, environmentsNotAlreadyEnabled); } async remapProjectsLinks(toDisable, toEnable) { const projectLinks = await this.projectStore.getProjectLinksForEnvironments(toDisable.map((env) => env.name)); const unlinkTasks = projectLinks.map((link) => { return this.forceRemoveEnvironmentFromProject(link.environmentName, link.projectId); }); await Promise.all(unlinkTasks.flat()); const uniqueProjects = [ ...new Set(projectLinks.map((link) => link.projectId)), ]; const linkTasks = uniqueProjects.flatMap((project) => { return toEnable.map((enabledEnv) => { return this.addEnvironmentToProject(enabledEnv.name, project, SYSTEM_USER_AUDIT); }); }); await Promise.all(linkTasks); } async forceRemoveEnvironmentFromProject(environment, projectId) { await this.featureEnvironmentStore.disconnectFeatures(environment, projectId); await this.featureEnvironmentStore.disconnectProject(environment, projectId); } async removeEnvironmentFromProject(environment, projectId, auditUser) { const _projectEnvs = await this.projectStore.getEnvironmentsForProject(projectId); await this.forceRemoveEnvironmentFromProject(environment, projectId); await this.eventService.storeEvent(new ProjectEnvironmentRemoved({ project: projectId, environment, auditUser, })); } } //# sourceMappingURL=environment-service.js.map