diff --git a/src/lib/features/export-import-toggles/createExportImportService.ts b/src/lib/features/export-import-toggles/createExportImportService.ts index 7d34a4b24530..00ee8da651c1 100644 --- a/src/lib/features/export-import-toggles/createExportImportService.ts +++ b/src/lib/features/export-import-toggles/createExportImportService.ts @@ -50,6 +50,10 @@ import { createFakeSegmentService, createSegmentService, } from '../segment/createSegmentService'; +import { + createDependentFeaturesService, + createFakeDependentFeaturesService, +} from '../dependent-features/createDependentFeaturesService'; export const createFakeExportImportTogglesService = ( config: IUnleashConfig, @@ -112,6 +116,8 @@ export const createFakeExportImportTogglesService = ( const segmentService = createFakeSegmentService(config); + const dependentFeaturesService = createFakeDependentFeaturesService(config); + const exportImportService = new ExportImportService( { importTogglesStore, @@ -133,6 +139,7 @@ export const createFakeExportImportTogglesService = ( strategyService, tagTypeService, segmentService, + dependentFeaturesService, }, dependentFeaturesReadModel, ); @@ -229,6 +236,11 @@ export const deferredExportImportTogglesService = ( const segmentService = createSegmentService(db, config); + const dependentFeaturesService = createDependentFeaturesService( + db, + config, + ); + const exportImportService = new ExportImportService( { importTogglesStore, @@ -250,6 +262,7 @@ export const deferredExportImportTogglesService = ( strategyService, tagTypeService, segmentService, + dependentFeaturesService, }, dependentFeaturesReadModel, ); diff --git a/src/lib/features/export-import-toggles/export-import-service.ts b/src/lib/features/export-import-toggles/export-import-service.ts index 9fd20b52b964..9c1a92e2c438 100644 --- a/src/lib/features/export-import-toggles/export-import-service.ts +++ b/src/lib/features/export-import-toggles/export-import-service.ts @@ -34,6 +34,7 @@ import { extractUsernameFromUser } from '../../util'; import { AccessService, ContextService, + DependentFeaturesService, EventService, FeatureTagService, FeatureToggleService, @@ -52,7 +53,6 @@ import { FeatureNameCheckResultWithFeaturePattern } from '../feature-toggle/feat import { IDependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model-type'; import groupBy from 'lodash.groupby'; import { ISegmentService } from '../../segments/segment-service-interface'; -import { FeatureDependenciesSchema } from '../../openapi/spec/feature-dependencies-schema'; export type IImportService = { validate( @@ -113,6 +113,8 @@ export default class ExportImportService private dependentFeaturesReadModel: IDependentFeaturesReadModel; + private dependentFeaturesService: DependentFeaturesService; + constructor( stores: Pick< IUnleashStores, @@ -138,6 +140,7 @@ export default class ExportImportService tagTypeService, featureTagService, segmentService, + dependentFeaturesService, }: Pick< IUnleashServices, | 'featureToggleService' @@ -148,6 +151,7 @@ export default class ExportImportService | 'tagTypeService' | 'featureTagService' | 'segmentService' + | 'dependentFeaturesService' >, dependentFeaturesReadModel: IDependentFeaturesReadModel, ) { @@ -168,6 +172,7 @@ export default class ExportImportService this.eventService = eventService; this.tagTypeService = tagTypeService; this.featureTagService = featureTagService; + this.dependentFeaturesService = dependentFeaturesService; this.importPermissionsService = new ImportPermissionsService( this.importTogglesStore, this.accessService, @@ -258,7 +263,7 @@ export default class ExportImportService ]); } - async importToggleLevelInfo( + async importFeatureData( dto: ImportTogglesSchema, user: User, ): Promise { @@ -274,9 +279,9 @@ export default class ExportImportService await this.importVerify(cleanedDto, user); - await this.importToggleLevelInfo(cleanedDto, user); + await this.importFeatureData(cleanedDto, user); - await this.importDefault(cleanedDto, user); + await this.importEnvironmentData(cleanedDto, user); await this.eventService.storeEvent({ project: cleanedDto.project, environment: cleanedDto.environment, @@ -285,10 +290,34 @@ export default class ExportImportService }); } - async importDefault(dto: ImportTogglesSchema, user: User): Promise { + async importEnvironmentData( + dto: ImportTogglesSchema, + user: User, + ): Promise { await this.deleteStrategies(dto); await this.importStrategies(dto, user); await this.importToggleStatuses(dto, user); + await this.importDependencies(dto, user); + } + + private async importDependencies(dto: ImportTogglesSchema, user: User) { + await Promise.all( + (dto.data.dependencies || []).flatMap((dependency) => { + const projectId = dto.data.features.find( + (feature) => feature.name === dependency.feature, + )!.project!; + return dependency.dependencies.map((parentDependency) => + this.dependentFeaturesService.upsertFeatureDependency( + { + child: dependency.feature, + projectId, + }, + parentDependency, + user, + ), + ); + }), + ); } private async importToggleStatuses(dto: ImportTogglesSchema, user: User) { diff --git a/src/lib/features/export-import-toggles/export-import.e2e.test.ts b/src/lib/features/export-import-toggles/export-import.e2e.test.ts index 50293a367c5b..516c8d590635 100644 --- a/src/lib/features/export-import-toggles/export-import.e2e.test.ts +++ b/src/lib/features/export-import-toggles/export-import.e2e.test.ts @@ -161,6 +161,7 @@ beforeAll(async () => { featuresExportImport: true, featureNamingPattern: true, dependentFeatures: true, + transactionalDecorator: true, }, }, }, @@ -750,6 +751,10 @@ test('import features to existing project and environment', async () => { ...defaultImportPayload, data: { ...defaultImportPayload.data, + features: [ + ...defaultImportPayload.data.features, + anotherExportedFeature, + ], featureStrategies: [ { ...exportedStrategy, @@ -762,6 +767,16 @@ test('import features to existing project and environment', async () => { name: segment.name, }, ], + dependencies: [ + { + feature: exportedFeature.name, + dependencies: [ + { + feature: anotherExportedFeature.name, + }, + ], + }, + ], }, }); @@ -779,6 +794,11 @@ test('import features to existing project and environment', async () => { ], }, ], + dependencies: [ + { + feature: anotherExportedFeature.name, + }, + ], }); const { body: importedFeatureEnvironment } = await getFeatureEnvironment( diff --git a/src/lib/features/feature-toggle/feature-toggle-service.ts b/src/lib/features/feature-toggle/feature-toggle-service.ts index db578f4b8af8..0e0b318665f2 100644 --- a/src/lib/features/feature-toggle/feature-toggle-service.ts +++ b/src/lib/features/feature-toggle/feature-toggle-service.ts @@ -1731,18 +1731,20 @@ class FeatureToggleService { ); if (hasDisabledStrategies && shouldActivateDisabledStrategies) { - strategies.map(async (strategy) => { - return this.updateStrategy( - strategy.id, - { disabled: false }, - { - environment, - projectId: project, - featureName, - }, - createdBy, - ); - }); + await Promise.all( + strategies.map((strategy) => + this.updateStrategy( + strategy.id, + { disabled: false }, + { + environment, + projectId: project, + featureName, + }, + createdBy, + ), + ), + ); } const hasOnlyDisabledStrategies = strategies.every(