mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	fix: clone variants (featureEnv and strategy) when cloning an env (#6026)
Fixes 2 bugs - Strategy variants - Feature env variants not being cloned when cloning an environment Closes # [SR-350](https://linear.app/unleash/issue/SR-350/cloning-environment-does-not-clone-variants-or-strategy-variants) Manual test verifies the fix <img width="1659" alt="Screenshot 2024-01-24 at 16 48 28" src="https://github.com/Unleash/unleash/assets/104830839/ba9fc9b8-e792-47bb-b6e8-660350384ea8"> <img width="1408" alt="Screenshot 2024-01-24 at 16 48 10" src="https://github.com/Unleash/unleash/assets/104830839/1e2d5287-35d0-42d2-9ab2-8caa313bd5a8"> --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
		
							parent
							
								
									41351a694e
								
							
						
					
					
						commit
						89bea0d532
					
				@ -152,7 +152,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
 | 
				
			|||||||
        await this.db('feature_environments')
 | 
					        await this.db('feature_environments')
 | 
				
			||||||
            .insert({ feature_name: featureName, environment, enabled })
 | 
					            .insert({ feature_name: featureName, environment, enabled })
 | 
				
			||||||
            .onConflict(['environment', 'feature_name'])
 | 
					            .onConflict(['environment', 'feature_name'])
 | 
				
			||||||
            .merge('enabled');
 | 
					            .merge(['enabled']);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: move to project store.
 | 
					    // TODO: move to project store.
 | 
				
			||||||
@ -366,8 +366,11 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
 | 
				
			|||||||
        projects: string[],
 | 
					        projects: string[],
 | 
				
			||||||
    ): Promise<void> {
 | 
					    ): Promise<void> {
 | 
				
			||||||
        await this.db.raw(
 | 
					        await this.db.raw(
 | 
				
			||||||
            `INSERT INTO ${T.featureEnvs} (
 | 
					            `INSERT INTO ${T.featureEnvs} (environment, feature_name, enabled, variants)
 | 
				
			||||||
                SELECT distinct ? AS environment, feature_name, enabled FROM ${T.featureEnvs} INNER JOIN ${T.features} ON ${T.featureEnvs}.feature_name = ${T.features}.name WHERE environment = ? AND project = ANY(?))`,
 | 
					             SELECT DISTINCT ? AS environemnt, fe.feature_name, fe.enabled, fe.variants
 | 
				
			||||||
 | 
					             FROM ${T.featureEnvs} AS fe
 | 
				
			||||||
 | 
					                      INNER JOIN ${T.features} AS f ON fe.feature_name = f.name
 | 
				
			||||||
 | 
					             WHERE fe.environment = ? AND f.project = ANY(?)`,
 | 
				
			||||||
            [destinationEnvironment, sourceEnvironment, projects],
 | 
					            [destinationEnvironment, sourceEnvironment, projects],
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -441,6 +444,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
 | 
				
			|||||||
                    parameters: JSON.stringify(featureStrategy.parameters),
 | 
					                    parameters: JSON.stringify(featureStrategy.parameters),
 | 
				
			||||||
                    constraints: JSON.stringify(featureStrategy.constraints),
 | 
					                    constraints: JSON.stringify(featureStrategy.constraints),
 | 
				
			||||||
                    sort_order: featureStrategy.sort_order,
 | 
					                    sort_order: featureStrategy.sort_order,
 | 
				
			||||||
 | 
					                    variants: JSON.stringify(featureStrategy.variants),
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { IUnleashStores } from '../../../lib/types';
 | 
					import { IFeatureStrategiesStore, IUnleashStores } from '../../../lib/types';
 | 
				
			||||||
import dbInit, { ITestDb } from '../helpers/database-init';
 | 
					import dbInit, { ITestDb } from '../helpers/database-init';
 | 
				
			||||||
import getLogger from '../../fixtures/no-logger';
 | 
					import getLogger from '../../fixtures/no-logger';
 | 
				
			||||||
import { IFeatureEnvironmentStore } from '../../../lib/types/stores/feature-environment-store';
 | 
					import { IFeatureEnvironmentStore } from '../../../lib/types/stores/feature-environment-store';
 | 
				
			||||||
@ -10,6 +10,7 @@ let stores: IUnleashStores;
 | 
				
			|||||||
let featureEnvironmentStore: IFeatureEnvironmentStore;
 | 
					let featureEnvironmentStore: IFeatureEnvironmentStore;
 | 
				
			||||||
let featureStore: IFeatureToggleStore;
 | 
					let featureStore: IFeatureToggleStore;
 | 
				
			||||||
let environmentStore: IEnvironmentStore;
 | 
					let environmentStore: IEnvironmentStore;
 | 
				
			||||||
 | 
					let featureStrategiesStore: IFeatureStrategiesStore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
beforeAll(async () => {
 | 
					beforeAll(async () => {
 | 
				
			||||||
    db = await dbInit('feature_environment_store_serial', getLogger);
 | 
					    db = await dbInit('feature_environment_store_serial', getLogger);
 | 
				
			||||||
@ -17,6 +18,7 @@ beforeAll(async () => {
 | 
				
			|||||||
    featureEnvironmentStore = stores.featureEnvironmentStore;
 | 
					    featureEnvironmentStore = stores.featureEnvironmentStore;
 | 
				
			||||||
    environmentStore = stores.environmentStore;
 | 
					    environmentStore = stores.environmentStore;
 | 
				
			||||||
    featureStore = stores.featureToggleStore;
 | 
					    featureStore = stores.featureToggleStore;
 | 
				
			||||||
 | 
					    featureStrategiesStore = stores.featureStrategiesStore;
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
afterAll(async () => {
 | 
					afterAll(async () => {
 | 
				
			||||||
@ -74,3 +76,100 @@ test('Setting enabled to not existing value returns 1', async () => {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
    expect(changed).toBe(1);
 | 
					    expect(changed).toBe(1);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('Copying features also copies variants', async () => {
 | 
				
			||||||
 | 
					    const envName = 'copy-env';
 | 
				
			||||||
 | 
					    const featureName = 'copy-env-toggle-feature';
 | 
				
			||||||
 | 
					    await environmentStore.create({
 | 
				
			||||||
 | 
					        name: envName,
 | 
				
			||||||
 | 
					        enabled: true,
 | 
				
			||||||
 | 
					        type: 'test',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await featureStore.create('default', {
 | 
				
			||||||
 | 
					        name: featureName,
 | 
				
			||||||
 | 
					        createdByUserId: 9999,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await featureEnvironmentStore.connectProject(envName, 'default');
 | 
				
			||||||
 | 
					    await featureEnvironmentStore.connectFeatures(envName, 'default');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const variant = {
 | 
				
			||||||
 | 
					        name: 'a',
 | 
				
			||||||
 | 
					        weight: 1,
 | 
				
			||||||
 | 
					        stickiness: 'default',
 | 
				
			||||||
 | 
					        weightType: 'fix' as any,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    await featureEnvironmentStore.setVariantsToFeatureEnvironments(
 | 
				
			||||||
 | 
					        featureName,
 | 
				
			||||||
 | 
					        [envName],
 | 
				
			||||||
 | 
					        [variant],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await environmentStore.create({
 | 
				
			||||||
 | 
					        name: 'clone',
 | 
				
			||||||
 | 
					        enabled: true,
 | 
				
			||||||
 | 
					        type: 'test',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await featureEnvironmentStore.connectProject('clone', 'default');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await featureEnvironmentStore.copyEnvironmentFeaturesByProjects(
 | 
				
			||||||
 | 
					        envName,
 | 
				
			||||||
 | 
					        'clone',
 | 
				
			||||||
 | 
					        ['default'],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const cloned = await featureEnvironmentStore.get({
 | 
				
			||||||
 | 
					        featureName: featureName,
 | 
				
			||||||
 | 
					        environment: 'clone',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(cloned.variants).toMatchObject([variant]);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('Copying strategies also copies strategy variants', async () => {
 | 
				
			||||||
 | 
					    const envName = 'copy-strategy';
 | 
				
			||||||
 | 
					    const featureName = 'copy-env-strategy-feature';
 | 
				
			||||||
 | 
					    await environmentStore.create({
 | 
				
			||||||
 | 
					        name: envName,
 | 
				
			||||||
 | 
					        enabled: true,
 | 
				
			||||||
 | 
					        type: 'test',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await featureStore.create('default', {
 | 
				
			||||||
 | 
					        name: featureName,
 | 
				
			||||||
 | 
					        createdByUserId: 9999,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await featureEnvironmentStore.connectProject(envName, 'default');
 | 
				
			||||||
 | 
					    await featureEnvironmentStore.connectFeatures(envName, 'default');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const strategyVariant = {
 | 
				
			||||||
 | 
					        name: 'a',
 | 
				
			||||||
 | 
					        weight: 1,
 | 
				
			||||||
 | 
					        stickiness: 'default',
 | 
				
			||||||
 | 
					        weightType: 'fix' as any,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    await featureStrategiesStore.createStrategyFeatureEnv({
 | 
				
			||||||
 | 
					        environment: envName,
 | 
				
			||||||
 | 
					        projectId: 'default',
 | 
				
			||||||
 | 
					        featureName,
 | 
				
			||||||
 | 
					        strategyName: 'default',
 | 
				
			||||||
 | 
					        variants: [strategyVariant],
 | 
				
			||||||
 | 
					        parameters: {},
 | 
				
			||||||
 | 
					        constraints: [],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await environmentStore.create({
 | 
				
			||||||
 | 
					        name: 'clone-2',
 | 
				
			||||||
 | 
					        enabled: true,
 | 
				
			||||||
 | 
					        type: 'test',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await featureEnvironmentStore.connectProject('clone-2', 'default');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await featureEnvironmentStore.cloneStrategies(envName, 'clone-2');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const clonedStrategy =
 | 
				
			||||||
 | 
					        await featureStrategiesStore.getStrategiesForFeatureEnv(
 | 
				
			||||||
 | 
					            'default',
 | 
				
			||||||
 | 
					            featureName,
 | 
				
			||||||
 | 
					            'clone-2',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    expect(clonedStrategy.length).toBe(1);
 | 
				
			||||||
 | 
					    expect(clonedStrategy[0].variants).toMatchObject([strategyVariant]);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user