From 2263a1f062bf8e957591cc5cf6cfb8ecdf55d065 Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Fri, 13 Oct 2023 14:17:54 +0300 Subject: [PATCH] feat: dependencies import validation (#5023) --- .../export-import-permissions.e2e.test.ts | 4 +- .../export-import-service.ts | 42 +++++++++++++++++++ .../export-import.e2e.test.ts | 19 ++++++++- .../import-validation-messages.ts | 14 ++++++- src/lib/openapi/spec/import-toggles-schema.ts | 4 ++ src/lib/openapi/spec/index.ts | 1 + 6 files changed, 78 insertions(+), 6 deletions(-) diff --git a/src/lib/features/export-import-toggles/export-import-permissions.e2e.test.ts b/src/lib/features/export-import-toggles/export-import-permissions.e2e.test.ts index a5e8327c74..127ad70b7e 100644 --- a/src/lib/features/export-import-toggles/export-import-permissions.e2e.test.ts +++ b/src/lib/features/export-import-toggles/export-import-permissions.e2e.test.ts @@ -340,7 +340,7 @@ test('validate import data', async () => { errors: [ { message: - 'We detected the following custom strategy in the import file that needs to be created first:', + 'We detected the following custom strategy that needs to be created first:', affectedItems: ['customStrategy'], }, { @@ -351,7 +351,7 @@ test('validate import data', async () => { { affectedItems: ['customSegment'], message: - 'We detected the following segments in the import file that need to be created first:', + 'We detected the following segments that need to be created first:', }, ], warnings: [ 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 653cd6dae9..9fd20b52b9 100644 --- a/src/lib/features/export-import-toggles/export-import-service.ts +++ b/src/lib/features/export-import-toggles/export-import-service.ts @@ -52,6 +52,7 @@ 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( @@ -194,6 +195,7 @@ export default class ExportImportService featureNameCheckResult, featureLimitResult, unsupportedSegments, + unsupportedDependencies, ] = await Promise.all([ this.getUnsupportedStrategies(dto), this.getUsedCustomStrategies(dto), @@ -210,6 +212,7 @@ export default class ExportImportService this.getInvalidFeatureNames(dto), this.getFeatureLimit(dto), this.getUnsupportedSegments(dto), + this.getMissingDependencies(dto), ]); const errors = ImportValidationMessages.compileErrors({ @@ -221,6 +224,7 @@ export default class ExportImportService featureNameCheckResult, featureLimitResult, segments: unsupportedSegments, + dependencies: unsupportedDependencies, }); const warnings = ImportValidationMessages.compileWarnings({ archivedFeatures, @@ -250,6 +254,7 @@ export default class ExportImportService this.importPermissionsService.verifyPermissions(dto, user, mode), this.verifyFeatures(dto), this.verifySegments(dto), + this.verifyDependencies(dto), ]); } @@ -459,6 +464,32 @@ export default class ExportImportService : []; } + private async getMissingDependencies( + dto: ImportTogglesSchema, + ): Promise { + const dependentFeatures = + dto.data.dependencies?.flatMap((dependency) => + dependency.dependencies.map((d) => d.feature), + ) || []; + const importedFeatures = dto.data.features.map((f) => f.name); + + const missingFromImported = dependentFeatures.filter( + (feature) => !importedFeatures.includes(feature), + ); + + let missingFeatures: string[] = []; + + if (missingFromImported.length) { + const featuresFromStore = ( + await this.toggleStore.getAllByNames(missingFromImported) + ).map((f) => f.name); + missingFeatures = missingFromImported.filter( + (feature) => !featuresFromStore.includes(feature), + ); + } + return missingFeatures; + } + private async verifySegments(dto: ImportTogglesSchema) { const unsupportedSegments = await this.getUnsupportedSegments(dto); if (unsupportedSegments.length > 0) { @@ -468,6 +499,17 @@ export default class ExportImportService } } + private async verifyDependencies(dto: ImportTogglesSchema) { + const unsupportedDependencies = await this.getMissingDependencies(dto); + if (unsupportedDependencies.length > 0) { + throw new BadDataError( + `The following dependent features are missing: ${unsupportedDependencies.join( + ', ', + )}`, + ); + } + } + private async verifyContextFields(dto: ImportTogglesSchema) { const unsupportedContextFields = await this.getUnsupportedContextFields( dto, 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 6395cf7007..50293a367c 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 @@ -1006,6 +1006,16 @@ test('validate import data', async () => { }, createdContextField, ], + dependencies: [ + { + feature: 'childFeature', + dependencies: [ + { + feature: 'parentFeature', + }, + ], + }, + ], }, }; @@ -1023,7 +1033,7 @@ test('validate import data', async () => { errors: [ { message: - 'We detected the following custom strategy in the import file that needs to be created first:', + 'We detected the following custom strategy that needs to be created first:', affectedItems: ['customStrategy'], }, { @@ -1051,9 +1061,14 @@ test('validate import data', async () => { }, { message: - 'We detected the following segments in the import file that need to be created first:', + 'We detected the following segments that need to be created first:', affectedItems: ['customSegment'], }, + { + affectedItems: ['parentFeature'], + message: + 'We detected the following dependencies that need to be created first:', + }, ], warnings: [ { diff --git a/src/lib/features/export-import-toggles/import-validation-messages.ts b/src/lib/features/export-import-toggles/import-validation-messages.ts index 450a64f14d..fe95b432ff 100644 --- a/src/lib/features/export-import-toggles/import-validation-messages.ts +++ b/src/lib/features/export-import-toggles/import-validation-messages.ts @@ -15,6 +15,7 @@ export interface IErrorsParams { featureNameCheckResult: FeatureNameCheckResultWithFeaturePattern; featureLimitResult: ProjectFeaturesLimit; segments: string[]; + dependencies: string[]; } export interface IWarningParams { @@ -48,13 +49,14 @@ export class ImportValidationMessages { featureNameCheckResult, featureLimitResult, segments, + dependencies, }: IErrorsParams): ImportTogglesValidateItemSchema[] { const errors: ImportTogglesValidateItemSchema[] = []; if (strategies.length > 0) { errors.push({ message: - 'We detected the following custom strategy in the import file that needs to be created first:', + 'We detected the following custom strategy that needs to be created first:', affectedItems: strategies.map((strategy) => strategy.name), }); } @@ -111,11 +113,19 @@ export class ImportValidationMessages { if (segments.length > 0) { errors.push({ message: - 'We detected the following segments in the import file that need to be created first:', + 'We detected the following segments that need to be created first:', affectedItems: segments, }); } + if (dependencies.length > 0) { + errors.push({ + message: + 'We detected the following dependencies that need to be created first:', + affectedItems: dependencies, + }); + } + return errors; } diff --git a/src/lib/openapi/spec/import-toggles-schema.ts b/src/lib/openapi/spec/import-toggles-schema.ts index fb56be4005..36f184679f 100644 --- a/src/lib/openapi/spec/import-toggles-schema.ts +++ b/src/lib/openapi/spec/import-toggles-schema.ts @@ -14,6 +14,8 @@ import { legalValueSchema } from './legal-value-schema'; import { tagTypeSchema } from './tag-type-schema'; import { featureEnvironmentSchema } from './feature-environment-schema'; import { strategyVariantSchema } from './strategy-variant-schema'; +import { featureDependenciesSchema } from './feature-dependencies-schema'; +import { dependentFeatureSchema } from './dependent-feature-schema'; export const importTogglesSchema = { $id: '#/components/schemas/importTogglesSchema', @@ -56,6 +58,8 @@ export const importTogglesSchema = { parametersSchema, legalValueSchema, tagTypeSchema, + featureDependenciesSchema, + dependentFeatureSchema, }, }, } as const; diff --git a/src/lib/openapi/spec/index.ts b/src/lib/openapi/spec/index.ts index 12e7d6f4bd..1db23c86fe 100644 --- a/src/lib/openapi/spec/index.ts +++ b/src/lib/openapi/spec/index.ts @@ -163,3 +163,4 @@ export * from './update-feature-strategy-segments-schema'; export * from './dependent-feature-schema'; export * from './create-dependent-feature-schema'; export * from './parent-feature-options-schema'; +export * from './feature-dependencies-schema';