mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-04 01:18:20 +02:00
fix: frontend variant weights distribution (#4347)
## About the changes Unit-tested way of distributing weights between variants.
This commit is contained in:
parent
7095e87061
commit
d2a4763eaa
117
frontend/src/component/common/util.test.ts
Normal file
117
frontend/src/component/common/util.test.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import { updateWeightEdit } from './util';
|
||||||
|
|
||||||
|
const variantTemplate = {
|
||||||
|
id: '0',
|
||||||
|
name: 'A',
|
||||||
|
weight: 0,
|
||||||
|
weightType: 'variable' as const,
|
||||||
|
stickiness: 'default',
|
||||||
|
isValid: true,
|
||||||
|
new: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('updateWeightEdit', () => {
|
||||||
|
it('can assign weight to one only variant', () => {
|
||||||
|
const variants = [variantTemplate];
|
||||||
|
expect(updateWeightEdit(variants, 100)).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "0",
|
||||||
|
"isValid": true,
|
||||||
|
"name": "A",
|
||||||
|
"new": false,
|
||||||
|
"stickiness": "default",
|
||||||
|
"weight": 100,
|
||||||
|
"weightType": "variable",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can distribute weight between 2 variants evenly', () => {
|
||||||
|
const variants = [
|
||||||
|
variantTemplate,
|
||||||
|
{ ...variantTemplate, id: '2', name: 'B' },
|
||||||
|
];
|
||||||
|
updateWeightEdit(variants, 100).forEach(variant => {
|
||||||
|
expect(variant).toHaveProperty('weight', 50);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can distribute weight between 8 variants evenly', () => {
|
||||||
|
const variants = Array.from({ length: 8 }, (_, i) => ({
|
||||||
|
...variantTemplate,
|
||||||
|
id: `${i}`,
|
||||||
|
name: `${i}`,
|
||||||
|
weight: i,
|
||||||
|
}));
|
||||||
|
updateWeightEdit(variants, 1000).forEach(variant => {
|
||||||
|
expect(variant).toHaveProperty('weight', 125);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can distribute weight between 8 variants evenly and assign the remainder to the last variant', () => {
|
||||||
|
const variants = Array.from({ length: 8 }, (_, i) => ({
|
||||||
|
...variantTemplate,
|
||||||
|
id: `${i}`,
|
||||||
|
name: `${i}`,
|
||||||
|
weight: i,
|
||||||
|
}));
|
||||||
|
const weights = updateWeightEdit(variants, 100).map(
|
||||||
|
variant => variant.weight
|
||||||
|
);
|
||||||
|
expect(weights).toEqual([13, 12, 13, 12, 13, 12, 13, 12]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can adjust variable weight to get correct sum', () => {
|
||||||
|
const variants = [
|
||||||
|
{ ...variantTemplate, weightType: 'fix' as const, weight: 333 },
|
||||||
|
{ ...variantTemplate, id: '2', name: 'B' },
|
||||||
|
];
|
||||||
|
const weights = updateWeightEdit(variants, 1000).map(
|
||||||
|
variant => variant.weight
|
||||||
|
);
|
||||||
|
expect(weights).toEqual([333, 667]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can deal with complex example', () => {
|
||||||
|
const variants = [
|
||||||
|
{ ...variantTemplate, weightType: 'fix' as const, weight: 333 },
|
||||||
|
{ ...variantTemplate, id: '2', name: 'B' },
|
||||||
|
{ ...variantTemplate, id: '3', name: 'C' },
|
||||||
|
{ ...variantTemplate, id: '4', name: 'D' },
|
||||||
|
{ ...variantTemplate, id: '5', name: 'E' },
|
||||||
|
{
|
||||||
|
...variantTemplate,
|
||||||
|
id: '6',
|
||||||
|
name: 'F',
|
||||||
|
weightType: 'fix' as const,
|
||||||
|
weight: 111,
|
||||||
|
},
|
||||||
|
{ ...variantTemplate, id: '7', name: 'G' },
|
||||||
|
{ ...variantTemplate, id: '8', name: 'H' },
|
||||||
|
];
|
||||||
|
const weights = updateWeightEdit(variants, 1000).map(
|
||||||
|
variant => variant.weight
|
||||||
|
);
|
||||||
|
expect(weights).toEqual([333, 93, 93, 93, 92, 111, 93, 92]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can deal with 0-weight variable variant', () => {
|
||||||
|
const variants = [
|
||||||
|
{ ...variantTemplate, weightType: 'fix' as const, weight: 500 },
|
||||||
|
{
|
||||||
|
...variantTemplate,
|
||||||
|
weightType: 'fix' as const,
|
||||||
|
weight: 500,
|
||||||
|
id: '2',
|
||||||
|
name: 'B',
|
||||||
|
},
|
||||||
|
{ ...variantTemplate, id: '3', name: 'C' },
|
||||||
|
];
|
||||||
|
const weights = updateWeightEdit(variants, 1000).map(
|
||||||
|
variant => variant.weight
|
||||||
|
);
|
||||||
|
expect(weights).toEqual([500, 500, 0]);
|
||||||
|
});
|
||||||
|
});
|
@ -98,11 +98,12 @@ export function updateWeightEdit(
|
|||||||
if (variants.length === 0) {
|
if (variants.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const { remainingPercentage, variableVariantCount } = variants.reduce(
|
let { remainingPercentage, variableVariantCount } = variants.reduce(
|
||||||
({ remainingPercentage, variableVariantCount }, variant) => {
|
({ remainingPercentage, variableVariantCount }, variant) => {
|
||||||
if (variant.weight && variant.weightType === weightTypes.FIX) {
|
if (variant.weight && variant.weightType === weightTypes.FIX) {
|
||||||
remainingPercentage -= Number(variant.weight);
|
remainingPercentage -= Number(variant.weight);
|
||||||
} else {
|
}
|
||||||
|
if (variant.weightType === weightTypes.VARIABLE) {
|
||||||
variableVariantCount += 1;
|
variableVariantCount += 1;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -113,14 +114,18 @@ export function updateWeightEdit(
|
|||||||
{ remainingPercentage: totalWeight, variableVariantCount: 0 }
|
{ remainingPercentage: totalWeight, variableVariantCount: 0 }
|
||||||
);
|
);
|
||||||
|
|
||||||
const percentage = parseInt(
|
const getPercentage = () =>
|
||||||
String(remainingPercentage / variableVariantCount)
|
Math.round(remainingPercentage / variableVariantCount);
|
||||||
);
|
|
||||||
|
|
||||||
return variants.map(variant => {
|
return variants.map(variant => {
|
||||||
if (variant.weightType !== weightTypes.FIX) {
|
if (variant.weightType !== weightTypes.FIX) {
|
||||||
|
const percentage = getPercentage(); // round "as we go" - clean best effort approach
|
||||||
|
remainingPercentage -= percentage;
|
||||||
|
variableVariantCount -= 1;
|
||||||
|
|
||||||
variant.weight = percentage;
|
variant.weight = percentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
return variant;
|
return variant;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ test('should render variants', async () => {
|
|||||||
name: 'variantName',
|
name: 'variantName',
|
||||||
stickiness: 'default',
|
stickiness: 'default',
|
||||||
weight: 1000,
|
weight: 1000,
|
||||||
weightType: 'variable',
|
weightType: 'variable' as const,
|
||||||
payload: {
|
payload: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
value: 'variantValue',
|
value: 'variantValue',
|
||||||
|
@ -52,7 +52,7 @@ export interface IFeatureVariant {
|
|||||||
name: string;
|
name: string;
|
||||||
stickiness: string;
|
stickiness: string;
|
||||||
weight: number;
|
weight: number;
|
||||||
weightType: string;
|
weightType: 'fix' | 'variable';
|
||||||
overrides?: IOverride[];
|
overrides?: IOverride[];
|
||||||
payload?: IPayload;
|
payload?: IPayload;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user