mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: dependencies import validation (#5023)
This commit is contained in:
		
							parent
							
								
									b6d945befc
								
							
						
					
					
						commit
						2263a1f062
					
				@ -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: [
 | 
			
		||||
 | 
			
		||||
@ -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<string[]> {
 | 
			
		||||
        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,
 | 
			
		||||
 | 
			
		||||
@ -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: [
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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';
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user