1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00

feat: Persist strategy variants (#4236)

This commit is contained in:
Mateusz Kwasniewski 2023-07-13 16:04:55 +02:00 committed by GitHub
parent 00ca1d7186
commit 87b46ea734
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 103 additions and 0 deletions

View File

@ -84,6 +84,7 @@ test('counts custom strategies in use', async () => {
environment: 'default',
parameters: {},
constraints: [],
variants: [],
});
// Act

View File

@ -15,6 +15,7 @@ import {
IFeatureToggleClient,
IFlagResolver,
IStrategyConfig,
IStrategyVariant,
ITag,
PartialDeep,
PartialSome,
@ -34,6 +35,7 @@ const COLUMNS = [
'title',
'parameters',
'constraints',
'variants',
'created_at',
'disabled',
];
@ -62,6 +64,7 @@ interface IFeatureStrategiesTable {
strategy_name: string;
parameters: object;
constraints: string;
variants: string;
sort_order: number;
created_at?: Date;
disabled?: boolean | null;
@ -84,6 +87,7 @@ function mapRow(row: IFeatureStrategiesTable): IFeatureStrategy {
title: row.title,
parameters: mapValues(row.parameters || {}, ensureStringValue),
constraints: (row.constraints as unknown as IConstraint[]) || [],
variants: (row.variants as unknown as IStrategyVariant[]) || [],
createdAt: row.created_at,
sortOrder: row.sort_order,
disabled: row.disabled,
@ -100,6 +104,7 @@ function mapInput(input: IFeatureStrategy): IFeatureStrategiesTable {
title: input.title,
parameters: input.parameters,
constraints: JSON.stringify(input.constraints || []),
variants: JSON.stringify(input.variants || []),
created_at: input.createdAt,
sort_order: input.sortOrder,
disabled: input.disabled,
@ -110,6 +115,7 @@ interface StrategyUpdate {
strategy_name: string;
parameters: object;
constraints: string;
variants: string;
title?: string;
disabled?: boolean;
}
@ -131,6 +137,7 @@ function mapStrategyUpdate(
update.disabled = input.disabled;
}
update.constraints = JSON.stringify(input.constraints || []);
update.variants = JSON.stringify(input.variants || []);
return update;
}
@ -599,6 +606,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
const strategy = {
name: r.strategy_name,
constraints: r.constraints || [],
variants: r.strategy_variants || [],
parameters: r.parameters,
sortOrder: r.sort_order,
id: r.strategy_id,

View File

@ -439,6 +439,14 @@ class FeatureToggleService {
strategyConfig.parameters.stickiness = 'default';
}
if (strategyConfig.variants && strategyConfig.variants.length > 0) {
await variantsArraySchema.validateAsync(strategyConfig.variants);
const fixedVariants = this.fixVariantWeights(
strategyConfig.variants,
);
strategyConfig.variants = fixedVariants;
}
try {
const newFeatureStrategy =
await this.featureStrategiesStore.createStrategyFeatureEnv({
@ -446,6 +454,7 @@ class FeatureToggleService {
title: strategyConfig.title,
disabled: strategyConfig.disabled,
constraints: strategyConfig.constraints || [],
variants: strategyConfig.variants || [],
parameters: strategyConfig.parameters || {},
sortOrder: strategyConfig.sortOrder,
projectId,
@ -562,6 +571,12 @@ class FeatureToggleService {
);
}
if (updates.variants && updates.variants.length > 0) {
await variantsArraySchema.validateAsync(updates.variants);
const fixedVariants = this.fixVariantWeights(updates.variants);
updates.variants = fixedVariants;
}
const strategy = await this.featureStrategiesStore.updateStrategy(
id,
updates,
@ -756,6 +771,7 @@ class FeatureToggleService {
name: strat.strategyName,
constraints: strat.constraints,
parameters: strat.parameters,
variants: strat.variants,
title: strat.title,
disabled: strat.disabled,
sortOrder: strat.sortOrder,

View File

@ -26,6 +26,7 @@ export interface IStrategyConfig {
name: string;
featureName?: string;
constraints?: IConstraint[];
variants?: IStrategyVariant[];
segments?: number[];
parameters?: { [key: string]: string };
sortOrder?: number;
@ -41,6 +42,7 @@ export interface IFeatureStrategy {
parameters: { [key: string]: string };
sortOrder?: number;
constraints: IConstraint[];
variants?: IStrategyVariant[];
createdAt?: Date;
segments?: number[];
title?: string | null;
@ -130,6 +132,8 @@ export interface IVariant {
}[];
}
export type IStrategyVariant = Omit<IVariant, 'overrides'>;
export interface IEnvironment {
name: string;
type: string;

View File

@ -2350,6 +2350,80 @@ test('Can create toggle with impression data on different project', async () =>
});
});
test('should handle strategy variants', async () => {
const feature = { name: uuidv4(), impressionData: false };
await app.createFeature(feature.name);
const strategyWithInvalidVariant = {
name: uuidv4(),
constraints: [],
variants: [
{
name: 'invalidVariant',
weight: 1000,
stickiness: 'default',
weightType: 'fix',
},
], // it should be variable
};
const variant = {
name: 'variantName',
weight: 1,
weightType: 'variable',
stickiness: 'default',
};
const updatedVariant1 = {
name: 'updatedVariant1',
weight: 500,
weightType: 'variable',
stickiness: 'default',
};
const updatedVariant2 = {
name: 'updatedVariant2',
weight: 500,
weightType: 'variable',
stickiness: 'default',
};
const strategyWithValidVariant = {
name: uuidv4(),
constraints: [],
variants: [variant],
};
const featureStrategiesPath = `/api/admin/projects/default/features/${feature.name}/environments/default/strategies`;
await app.request
.post(featureStrategiesPath)
.send(strategyWithInvalidVariant)
.expect(400);
await createStrategy(feature.name, strategyWithValidVariant);
const { body: strategies } = await app.request.get(featureStrategiesPath);
expect(strategies).toMatchObject([
{
variants: [{ ...variant, weight: 1000 }], // weight was fixed
},
]);
await updateStrategy(feature.name, strategies[0].id, {
...strategies[0],
variants: [updatedVariant1, updatedVariant2],
});
const { body: updatedStrategies } = await app.request.get(
featureStrategiesPath,
);
expect(updatedStrategies).toMatchObject([
{
variants: [updatedVariant1, updatedVariant2],
},
]);
});
test('should reject invalid constraint values for multi-valued constraints', async () => {
const project = await db.stores.projectStore.create({
id: uuidv4(),