mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	fix: enable segment importing for oss (#5010)
This commit is contained in:
		
							parent
							
								
									66304cf8e7
								
							
						
					
					
						commit
						7b7a2a706c
					
				@ -46,6 +46,10 @@ import {
 | 
				
			|||||||
import { DbServiceFactory } from 'lib/db/transaction';
 | 
					import { DbServiceFactory } from 'lib/db/transaction';
 | 
				
			||||||
import { DependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model';
 | 
					import { DependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model';
 | 
				
			||||||
import { FakeDependentFeaturesReadModel } from '../dependent-features/fake-dependent-features-read-model';
 | 
					import { FakeDependentFeaturesReadModel } from '../dependent-features/fake-dependent-features-read-model';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    createFakeSegmentService,
 | 
				
			||||||
 | 
					    createSegmentService,
 | 
				
			||||||
 | 
					} from '../segment/createSegmentService';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const createFakeExportImportTogglesService = (
 | 
					export const createFakeExportImportTogglesService = (
 | 
				
			||||||
    config: IUnleashConfig,
 | 
					    config: IUnleashConfig,
 | 
				
			||||||
@ -106,6 +110,8 @@ export const createFakeExportImportTogglesService = (
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
    const dependentFeaturesReadModel = new FakeDependentFeaturesReadModel();
 | 
					    const dependentFeaturesReadModel = new FakeDependentFeaturesReadModel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const segmentService = createFakeSegmentService(config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const exportImportService = new ExportImportService(
 | 
					    const exportImportService = new ExportImportService(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            importTogglesStore,
 | 
					            importTogglesStore,
 | 
				
			||||||
@ -126,6 +132,7 @@ export const createFakeExportImportTogglesService = (
 | 
				
			|||||||
            contextService,
 | 
					            contextService,
 | 
				
			||||||
            strategyService,
 | 
					            strategyService,
 | 
				
			||||||
            tagTypeService,
 | 
					            tagTypeService,
 | 
				
			||||||
 | 
					            segmentService,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        dependentFeaturesReadModel,
 | 
					        dependentFeaturesReadModel,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
@ -220,6 +227,8 @@ export const deferredExportImportTogglesService = (
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
        const dependentFeaturesReadModel = new DependentFeaturesReadModel(db);
 | 
					        const dependentFeaturesReadModel = new DependentFeaturesReadModel(db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const segmentService = createSegmentService(db, config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const exportImportService = new ExportImportService(
 | 
					        const exportImportService = new ExportImportService(
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                importTogglesStore,
 | 
					                importTogglesStore,
 | 
				
			||||||
@ -240,6 +249,7 @@ export const deferredExportImportTogglesService = (
 | 
				
			|||||||
                contextService,
 | 
					                contextService,
 | 
				
			||||||
                strategyService,
 | 
					                strategyService,
 | 
				
			||||||
                tagTypeService,
 | 
					                tagTypeService,
 | 
				
			||||||
 | 
					                segmentService,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            dependentFeaturesReadModel,
 | 
					            dependentFeaturesReadModel,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
				
			|||||||
@ -170,9 +170,18 @@ const tags = [
 | 
				
			|||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tagTypes = [
 | 
					const tagTypes = [
 | 
				
			||||||
    { name: 'bestt', description: 'test' },
 | 
					    {
 | 
				
			||||||
    { name: 'special_tag', description: 'this is my special tag' },
 | 
					        name: 'bestt',
 | 
				
			||||||
    { name: 'special_tag', description: 'this is my special tag' }, // deliberate duplicate
 | 
					        description: 'test',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        name: 'special_tag',
 | 
				
			||||||
 | 
					        description: 'this is my special tag',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        name: 'special_tag',
 | 
				
			||||||
 | 
					        description: 'this is my special tag',
 | 
				
			||||||
 | 
					    }, // deliberate duplicate
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const importPayload: ImportTogglesSchema = {
 | 
					const importPayload: ImportTogglesSchema = {
 | 
				
			||||||
@ -199,13 +208,19 @@ const importPayload: ImportTogglesSchema = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const createUserEditorAccess = async (name, email) => {
 | 
					const createUserEditorAccess = async (name, email) => {
 | 
				
			||||||
    const { userStore } = stores;
 | 
					    const { userStore } = stores;
 | 
				
			||||||
    const user = await userStore.insert({ name, email });
 | 
					    const user = await userStore.insert({
 | 
				
			||||||
 | 
					        name,
 | 
				
			||||||
 | 
					        email,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    return user;
 | 
					    return user;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const createUserAdminAccess = async (name, email) => {
 | 
					const createUserAdminAccess = async (name, email) => {
 | 
				
			||||||
    const { userStore } = stores;
 | 
					    const { userStore } = stores;
 | 
				
			||||||
    const user = await userStore.insert({ name, email });
 | 
					    const user = await userStore.insert({
 | 
				
			||||||
 | 
					        name,
 | 
				
			||||||
 | 
					        email,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    await accessService.addUserToRole(user.id, adminRole.id, 'default');
 | 
					    await accessService.addUserToRole(user.id, adminRole.id, 'default');
 | 
				
			||||||
    return user;
 | 
					    return user;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -301,7 +316,12 @@ test('validate import data', async () => {
 | 
				
			|||||||
        data: {
 | 
					        data: {
 | 
				
			||||||
            ...importPayload.data,
 | 
					            ...importPayload.data,
 | 
				
			||||||
            featureStrategies: [{ name: 'customStrategy' }],
 | 
					            featureStrategies: [{ name: 'customStrategy' }],
 | 
				
			||||||
            segments: [{ id: 1, name: 'customSegment' }],
 | 
					            segments: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    id: 1,
 | 
				
			||||||
 | 
					                    name: 'customSegment',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
            contextFields: [
 | 
					            contextFields: [
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    ...contextField,
 | 
					                    ...contextField,
 | 
				
			||||||
@ -328,6 +348,11 @@ test('validate import data', async () => {
 | 
				
			|||||||
                    'We detected the following context fields that do not have matching legal values with the imported ones:',
 | 
					                    'We detected the following context fields that do not have matching legal values with the imported ones:',
 | 
				
			||||||
                affectedItems: [contextField.name],
 | 
					                affectedItems: [contextField.name],
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                affectedItems: ['customSegment'],
 | 
				
			||||||
 | 
					                message:
 | 
				
			||||||
 | 
					                    'We detected the following segments in the import file that need to be created first:',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        warnings: [
 | 
					        warnings: [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
				
			|||||||
@ -51,6 +51,7 @@ import { findDuplicates } from '../../util/findDuplicates';
 | 
				
			|||||||
import { FeatureNameCheckResultWithFeaturePattern } from '../feature-toggle/feature-toggle-service';
 | 
					import { FeatureNameCheckResultWithFeaturePattern } from '../feature-toggle/feature-toggle-service';
 | 
				
			||||||
import { IDependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model-type';
 | 
					import { IDependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model-type';
 | 
				
			||||||
import groupBy from 'lodash.groupby';
 | 
					import groupBy from 'lodash.groupby';
 | 
				
			||||||
 | 
					import { ISegmentService } from '../../segments/segment-service-interface';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type IImportService = {
 | 
					export type IImportService = {
 | 
				
			||||||
    validate(
 | 
					    validate(
 | 
				
			||||||
@ -103,6 +104,8 @@ export default class ExportImportService
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private tagTypeService: TagTypeService;
 | 
					    private tagTypeService: TagTypeService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private segmentService: ISegmentService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private featureTagService: FeatureTagService;
 | 
					    private featureTagService: FeatureTagService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private importPermissionsService: ImportPermissionsService;
 | 
					    private importPermissionsService: ImportPermissionsService;
 | 
				
			||||||
@ -133,6 +136,7 @@ export default class ExportImportService
 | 
				
			|||||||
            eventService,
 | 
					            eventService,
 | 
				
			||||||
            tagTypeService,
 | 
					            tagTypeService,
 | 
				
			||||||
            featureTagService,
 | 
					            featureTagService,
 | 
				
			||||||
 | 
					            segmentService,
 | 
				
			||||||
        }: Pick<
 | 
					        }: Pick<
 | 
				
			||||||
            IUnleashServices,
 | 
					            IUnleashServices,
 | 
				
			||||||
            | 'featureToggleService'
 | 
					            | 'featureToggleService'
 | 
				
			||||||
@ -142,6 +146,7 @@ export default class ExportImportService
 | 
				
			|||||||
            | 'eventService'
 | 
					            | 'eventService'
 | 
				
			||||||
            | 'tagTypeService'
 | 
					            | 'tagTypeService'
 | 
				
			||||||
            | 'featureTagService'
 | 
					            | 'featureTagService'
 | 
				
			||||||
 | 
					            | 'segmentService'
 | 
				
			||||||
        >,
 | 
					        >,
 | 
				
			||||||
        dependentFeaturesReadModel: IDependentFeaturesReadModel,
 | 
					        dependentFeaturesReadModel: IDependentFeaturesReadModel,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
@ -158,6 +163,7 @@ export default class ExportImportService
 | 
				
			|||||||
        this.strategyService = strategyService;
 | 
					        this.strategyService = strategyService;
 | 
				
			||||||
        this.contextService = contextService;
 | 
					        this.contextService = contextService;
 | 
				
			||||||
        this.accessService = accessService;
 | 
					        this.accessService = accessService;
 | 
				
			||||||
 | 
					        this.segmentService = segmentService;
 | 
				
			||||||
        this.eventService = eventService;
 | 
					        this.eventService = eventService;
 | 
				
			||||||
        this.tagTypeService = tagTypeService;
 | 
					        this.tagTypeService = tagTypeService;
 | 
				
			||||||
        this.featureTagService = featureTagService;
 | 
					        this.featureTagService = featureTagService;
 | 
				
			||||||
@ -187,6 +193,7 @@ export default class ExportImportService
 | 
				
			|||||||
            duplicateFeatures,
 | 
					            duplicateFeatures,
 | 
				
			||||||
            featureNameCheckResult,
 | 
					            featureNameCheckResult,
 | 
				
			||||||
            featureLimitResult,
 | 
					            featureLimitResult,
 | 
				
			||||||
 | 
					            unsupportedSegments,
 | 
				
			||||||
        ] = await Promise.all([
 | 
					        ] = await Promise.all([
 | 
				
			||||||
            this.getUnsupportedStrategies(dto),
 | 
					            this.getUnsupportedStrategies(dto),
 | 
				
			||||||
            this.getUsedCustomStrategies(dto),
 | 
					            this.getUsedCustomStrategies(dto),
 | 
				
			||||||
@ -202,6 +209,7 @@ export default class ExportImportService
 | 
				
			|||||||
            this.getDuplicateFeatures(dto),
 | 
					            this.getDuplicateFeatures(dto),
 | 
				
			||||||
            this.getInvalidFeatureNames(dto),
 | 
					            this.getInvalidFeatureNames(dto),
 | 
				
			||||||
            this.getFeatureLimit(dto),
 | 
					            this.getFeatureLimit(dto),
 | 
				
			||||||
 | 
					            this.getUnsupportedSegments(dto),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const errors = ImportValidationMessages.compileErrors({
 | 
					        const errors = ImportValidationMessages.compileErrors({
 | 
				
			||||||
@ -212,6 +220,7 @@ export default class ExportImportService
 | 
				
			|||||||
            duplicateFeatures,
 | 
					            duplicateFeatures,
 | 
				
			||||||
            featureNameCheckResult,
 | 
					            featureNameCheckResult,
 | 
				
			||||||
            featureLimitResult,
 | 
					            featureLimitResult,
 | 
				
			||||||
 | 
					            segments: unsupportedSegments,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        const warnings = ImportValidationMessages.compileWarnings({
 | 
					        const warnings = ImportValidationMessages.compileWarnings({
 | 
				
			||||||
            archivedFeatures,
 | 
					            archivedFeatures,
 | 
				
			||||||
@ -240,6 +249,7 @@ export default class ExportImportService
 | 
				
			|||||||
            this.verifyContextFields(dto),
 | 
					            this.verifyContextFields(dto),
 | 
				
			||||||
            this.importPermissionsService.verifyPermissions(dto, user, mode),
 | 
					            this.importPermissionsService.verifyPermissions(dto, user, mode),
 | 
				
			||||||
            this.verifyFeatures(dto),
 | 
					            this.verifyFeatures(dto),
 | 
				
			||||||
 | 
					            this.verifySegments(dto),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -426,6 +436,38 @@ export default class ExportImportService
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private async getUnsupportedSegments(
 | 
				
			||||||
 | 
					        dto: ImportTogglesSchema,
 | 
				
			||||||
 | 
					    ): Promise<string[]> {
 | 
				
			||||||
 | 
					        const supportedSegments = await this.segmentService.getAll();
 | 
				
			||||||
 | 
					        const targetProject = dto.project;
 | 
				
			||||||
 | 
					        return dto.data.segments
 | 
				
			||||||
 | 
					            ? dto.data.segments
 | 
				
			||||||
 | 
					                  .filter(
 | 
				
			||||||
 | 
					                      (importingSegment) =>
 | 
				
			||||||
 | 
					                          !supportedSegments.find(
 | 
				
			||||||
 | 
					                              (existingSegment) =>
 | 
				
			||||||
 | 
					                                  importingSegment.name ===
 | 
				
			||||||
 | 
					                                      existingSegment.name &&
 | 
				
			||||||
 | 
					                                  (!existingSegment.project ||
 | 
				
			||||||
 | 
					                                      existingSegment.project ===
 | 
				
			||||||
 | 
					                                          targetProject),
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  .map((it) => it.name)
 | 
				
			||||||
 | 
					            : [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private async verifySegments(dto: ImportTogglesSchema) {
 | 
				
			||||||
 | 
					        const unsupportedSegments = await this.getUnsupportedSegments(dto);
 | 
				
			||||||
 | 
					        if (unsupportedSegments.length > 0) {
 | 
				
			||||||
 | 
					            throw new BadDataError(
 | 
				
			||||||
 | 
					                `Unsupported segments: ${unsupportedSegments.join(', ')}`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private async verifyContextFields(dto: ImportTogglesSchema) {
 | 
					    private async verifyContextFields(dto: ImportTogglesSchema) {
 | 
				
			||||||
        const unsupportedContextFields = await this.getUnsupportedContextFields(
 | 
					        const unsupportedContextFields = await this.getUnsupportedContextFields(
 | 
				
			||||||
            dto,
 | 
					            dto,
 | 
				
			||||||
 | 
				
			|||||||
@ -1018,6 +1018,11 @@ test('validate import data', async () => {
 | 
				
			|||||||
                    'We detected you want to create 2 new features to a project that already has 0 existing features, exceeding the maximum limit of 1.',
 | 
					                    'We detected you want to create 2 new features to a project that already has 0 existing features, exceeding the maximum limit of 1.',
 | 
				
			||||||
                affectedItems: [],
 | 
					                affectedItems: [],
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                message:
 | 
				
			||||||
 | 
					                    'We detected the following segments in the import file that need to be created first:',
 | 
				
			||||||
 | 
					                affectedItems: ['customSegment'],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        warnings: [
 | 
					        warnings: [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,7 @@ export interface IErrorsParams {
 | 
				
			|||||||
    duplicateFeatures: string[];
 | 
					    duplicateFeatures: string[];
 | 
				
			||||||
    featureNameCheckResult: FeatureNameCheckResultWithFeaturePattern;
 | 
					    featureNameCheckResult: FeatureNameCheckResultWithFeaturePattern;
 | 
				
			||||||
    featureLimitResult: ProjectFeaturesLimit;
 | 
					    featureLimitResult: ProjectFeaturesLimit;
 | 
				
			||||||
 | 
					    segments: string[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IWarningParams {
 | 
					export interface IWarningParams {
 | 
				
			||||||
@ -46,6 +47,7 @@ export class ImportValidationMessages {
 | 
				
			|||||||
        duplicateFeatures,
 | 
					        duplicateFeatures,
 | 
				
			||||||
        featureNameCheckResult,
 | 
					        featureNameCheckResult,
 | 
				
			||||||
        featureLimitResult,
 | 
					        featureLimitResult,
 | 
				
			||||||
 | 
					        segments,
 | 
				
			||||||
    }: IErrorsParams): ImportTogglesValidateItemSchema[] {
 | 
					    }: IErrorsParams): ImportTogglesValidateItemSchema[] {
 | 
				
			||||||
        const errors: ImportTogglesValidateItemSchema[] = [];
 | 
					        const errors: ImportTogglesValidateItemSchema[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -106,6 +108,14 @@ 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:',
 | 
				
			||||||
 | 
					                affectedItems: segments,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return errors;
 | 
					        return errors;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -142,7 +142,7 @@ export const exportResultSchema = {
 | 
				
			|||||||
            items: {
 | 
					            items: {
 | 
				
			||||||
                type: 'object',
 | 
					                type: 'object',
 | 
				
			||||||
                additionalProperties: false,
 | 
					                additionalProperties: false,
 | 
				
			||||||
                required: ['id'],
 | 
					                required: ['id', 'name'],
 | 
				
			||||||
                properties: {
 | 
					                properties: {
 | 
				
			||||||
                    id: {
 | 
					                    id: {
 | 
				
			||||||
                        type: 'number',
 | 
					                        type: 'number',
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user