mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	fix: don't create duplicate tags on import (#3688)
This commit is contained in:
		
							parent
							
								
									c21fafc8aa
								
							
						
					
					
						commit
						108e15940e
					
				@ -280,18 +280,18 @@ export default class ExportImportService {
 | 
				
			|||||||
        await this.importTogglesStore.deleteTagsForFeatures(
 | 
					        await this.importTogglesStore.deleteTagsForFeatures(
 | 
				
			||||||
            dto.data.features.map((feature) => feature.name),
 | 
					            dto.data.features.map((feature) => feature.name),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        return Promise.all(
 | 
					
 | 
				
			||||||
            (dto.data.featureTags || []).map((tag) => {
 | 
					        const featureTags = dto.data.featureTags || [];
 | 
				
			||||||
                return tag.tagType
 | 
					        for (const tag of featureTags) {
 | 
				
			||||||
                    ? this.featureTagService.addTag(
 | 
					            if (tag.tagType) {
 | 
				
			||||||
 | 
					                await this.featureTagService.addTag(
 | 
				
			||||||
                    tag.featureName,
 | 
					                    tag.featureName,
 | 
				
			||||||
                    { type: tag.tagType, value: tag.tagValue },
 | 
					                    { type: tag.tagType, value: tag.tagValue },
 | 
				
			||||||
                    extractUsernameFromUser(user),
 | 
					                    extractUsernameFromUser(user),
 | 
				
			||||||
                      )
 | 
					 | 
				
			||||||
                    : Promise.resolve();
 | 
					 | 
				
			||||||
            }),
 | 
					 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private async importContextFields(dto: ImportTogglesSchema, user: User) {
 | 
					    private async importContextFields(dto: ImportTogglesSchema, user: User) {
 | 
				
			||||||
        const newContextFields = (await this.getNewContextFields(dto)) || [];
 | 
					        const newContextFields = (await this.getNewContextFields(dto)) || [];
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,7 @@ import {
 | 
				
			|||||||
    IProjectStore,
 | 
					    IProjectStore,
 | 
				
			||||||
    ISegment,
 | 
					    ISegment,
 | 
				
			||||||
    IStrategyConfig,
 | 
					    IStrategyConfig,
 | 
				
			||||||
 | 
					    ITagStore,
 | 
				
			||||||
    IVariant,
 | 
					    IVariant,
 | 
				
			||||||
} from '../../types';
 | 
					} from '../../types';
 | 
				
			||||||
import { DEFAULT_ENV } from '../../util';
 | 
					import { DEFAULT_ENV } from '../../util';
 | 
				
			||||||
@ -33,6 +34,7 @@ let environmentStore: IEnvironmentStore;
 | 
				
			|||||||
let contextFieldStore: IContextFieldStore;
 | 
					let contextFieldStore: IContextFieldStore;
 | 
				
			||||||
let projectStore: IProjectStore;
 | 
					let projectStore: IProjectStore;
 | 
				
			||||||
let toggleStore: IFeatureToggleStore;
 | 
					let toggleStore: IFeatureToggleStore;
 | 
				
			||||||
 | 
					let tagStore: ITagStore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const defaultStrategy: IStrategyConfig = {
 | 
					const defaultStrategy: IStrategyConfig = {
 | 
				
			||||||
    name: 'default',
 | 
					    name: 'default',
 | 
				
			||||||
@ -150,6 +152,7 @@ beforeAll(async () => {
 | 
				
			|||||||
    projectStore = db.stores.projectStore;
 | 
					    projectStore = db.stores.projectStore;
 | 
				
			||||||
    contextFieldStore = db.stores.contextFieldStore;
 | 
					    contextFieldStore = db.stores.contextFieldStore;
 | 
				
			||||||
    toggleStore = db.stores.featureToggleStore;
 | 
					    toggleStore = db.stores.featureToggleStore;
 | 
				
			||||||
 | 
					    tagStore = db.stores.tagStore;
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
beforeEach(async () => {
 | 
					beforeEach(async () => {
 | 
				
			||||||
@ -157,6 +160,7 @@ beforeEach(async () => {
 | 
				
			|||||||
    await toggleStore.deleteAll();
 | 
					    await toggleStore.deleteAll();
 | 
				
			||||||
    await projectStore.deleteAll();
 | 
					    await projectStore.deleteAll();
 | 
				
			||||||
    await environmentStore.deleteAll();
 | 
					    await environmentStore.deleteAll();
 | 
				
			||||||
 | 
					    await tagStore.deleteAll();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await contextFieldStore.deleteAll();
 | 
					    await contextFieldStore.deleteAll();
 | 
				
			||||||
    await app.createContextField({ name: 'appName' });
 | 
					    await app.createContextField({ name: 'appName' });
 | 
				
			||||||
@ -544,6 +548,10 @@ const exportedFeature: ImportTogglesSchema['data']['features'][0] = {
 | 
				
			|||||||
    project: 'old_project',
 | 
					    project: 'old_project',
 | 
				
			||||||
    name: 'first_feature',
 | 
					    name: 'first_feature',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					const anotherExportedFeature: ImportTogglesSchema['data']['features'][0] = {
 | 
				
			||||||
 | 
					    project: 'old_project',
 | 
				
			||||||
 | 
					    name: 'second_feature',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
const constraints: ImportTogglesSchema['data']['featureStrategies'][0]['constraints'] =
 | 
					const constraints: ImportTogglesSchema['data']['featureStrategies'][0]['constraints'] =
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -614,6 +622,31 @@ const defaultImportPayload: ImportTogglesSchema = {
 | 
				
			|||||||
    environment: DEFAULT_ENV,
 | 
					    environment: DEFAULT_ENV,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const importWithMultipleFeatures: ImportTogglesSchema = {
 | 
				
			||||||
 | 
					    data: {
 | 
				
			||||||
 | 
					        features: [exportedFeature, anotherExportedFeature],
 | 
				
			||||||
 | 
					        featureStrategies: [],
 | 
				
			||||||
 | 
					        featureEnvironments: [],
 | 
				
			||||||
 | 
					        featureTags: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                featureName: exportedFeature.name,
 | 
				
			||||||
 | 
					                tagType: 'simple',
 | 
				
			||||||
 | 
					                tagValue: 'tag1',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                featureName: anotherExportedFeature.name,
 | 
				
			||||||
 | 
					                tagType: 'simple',
 | 
				
			||||||
 | 
					                tagValue: 'tag1',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        tagTypes,
 | 
				
			||||||
 | 
					        contextFields: [],
 | 
				
			||||||
 | 
					        segments: [],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    project: DEFAULT_PROJECT,
 | 
				
			||||||
 | 
					    environment: DEFAULT_ENV,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getFeature = async (feature: string) =>
 | 
					const getFeature = async (feature: string) =>
 | 
				
			||||||
    app.request
 | 
					    app.request
 | 
				
			||||||
        .get(`/api/admin/projects/${DEFAULT_PROJECT}/features/${feature}`)
 | 
					        .get(`/api/admin/projects/${DEFAULT_PROJECT}/features/${feature}`)
 | 
				
			||||||
@ -672,6 +705,24 @@ test('import features to existing project and environment', async () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('import multiple features with same tag', async () => {
 | 
				
			||||||
 | 
					    await createProjects();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await app.importToggles(importWithMultipleFeatures);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { body: tags1 } = await getTags(exportedFeature.name);
 | 
				
			||||||
 | 
					    const { body: tags2 } = await getTags(anotherExportedFeature.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(tags1).toMatchObject({
 | 
				
			||||||
 | 
					        version: 1,
 | 
				
			||||||
 | 
					        tags: [{ value: 'tag1', type: 'simple' }],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(tags2).toMatchObject({
 | 
				
			||||||
 | 
					        version: 1,
 | 
				
			||||||
 | 
					        tags: [{ value: 'tag1', type: 'simple' }],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test('importing same JSON should work multiple times in a row', async () => {
 | 
					test('importing same JSON should work multiple times in a row', async () => {
 | 
				
			||||||
    await createProjects();
 | 
					    await createProjects();
 | 
				
			||||||
    await app.importToggles(defaultImportPayload);
 | 
					    await app.importToggles(defaultImportPayload);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user