Skip to content

Commit

Permalink
fix: clone variants (featureEnv and strategy) when cloning an env (#6026
Browse files Browse the repository at this point in the history
)

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 <[email protected]>
  • Loading branch information
andreas-unleash authored Jan 25, 2024
1 parent 41351a6 commit 89bea0d
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 4 deletions.
10 changes: 7 additions & 3 deletions src/lib/db/feature-environment-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
await this.db('feature_environments')
.insert({ feature_name: featureName, environment, enabled })
.onConflict(['environment', 'feature_name'])
.merge('enabled');
.merge(['enabled']);
}

// TODO: move to project store.
Expand Down Expand Up @@ -366,8 +366,11 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
projects: string[],
): Promise<void> {
await this.db.raw(
`INSERT INTO ${T.featureEnvs} (
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(?))`,
`INSERT INTO ${T.featureEnvs} (environment, feature_name, enabled, variants)
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],
);
}
Expand Down Expand Up @@ -441,6 +444,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
parameters: JSON.stringify(featureStrategy.parameters),
constraints: JSON.stringify(featureStrategy.constraints),
sort_order: featureStrategy.sort_order,
variants: JSON.stringify(featureStrategy.variants),
};
},
);
Expand Down
101 changes: 100 additions & 1 deletion src/test/e2e/stores/feature-environment-store.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IUnleashStores } from '../../../lib/types';
import { IFeatureStrategiesStore, IUnleashStores } from '../../../lib/types';
import dbInit, { ITestDb } from '../helpers/database-init';
import getLogger from '../../fixtures/no-logger';
import { IFeatureEnvironmentStore } from '../../../lib/types/stores/feature-environment-store';
Expand All @@ -10,13 +10,15 @@ let stores: IUnleashStores;
let featureEnvironmentStore: IFeatureEnvironmentStore;
let featureStore: IFeatureToggleStore;
let environmentStore: IEnvironmentStore;
let featureStrategiesStore: IFeatureStrategiesStore;

beforeAll(async () => {
db = await dbInit('feature_environment_store_serial', getLogger);
stores = db.stores;
featureEnvironmentStore = stores.featureEnvironmentStore;
environmentStore = stores.environmentStore;
featureStore = stores.featureToggleStore;
featureStrategiesStore = stores.featureStrategiesStore;
});

afterAll(async () => {
Expand Down Expand Up @@ -74,3 +76,100 @@ test('Setting enabled to not existing value returns 1', async () => {
);
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]);
});

0 comments on commit 89bea0d

Please sign in to comment.