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:
parent
00ca1d7186
commit
87b46ea734
@ -84,6 +84,7 @@ test('counts custom strategies in use', async () => {
|
||||
environment: 'default',
|
||||
parameters: {},
|
||||
constraints: [],
|
||||
variants: [],
|
||||
});
|
||||
|
||||
// Act
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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(),
|
||||
|
Loading…
Reference in New Issue
Block a user