mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-29 01:15:48 +02:00
fix: prevent strategy variant weight from going into negative numbers on Frontend (#7460)
Added validation if sum goes over 100%. Remaining split is never negative
This commit is contained in:
parent
1cdbd21212
commit
083273b49b
@ -114,4 +114,70 @@ describe('updateWeightEdit', () => {
|
|||||||
);
|
);
|
||||||
expect(weights).toEqual([500, 500, 0]);
|
expect(weights).toEqual([500, 500, 0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('sum over 100% does not result in negative weight', () => {
|
||||||
|
it('when 2 items exceed 100%', () => {
|
||||||
|
const variants = [
|
||||||
|
{
|
||||||
|
...variantTemplate,
|
||||||
|
weightType: 'fix' as const,
|
||||||
|
weight: 600,
|
||||||
|
id: '1',
|
||||||
|
name: 'A',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...variantTemplate,
|
||||||
|
weightType: 'fix' as const,
|
||||||
|
weight: 600,
|
||||||
|
id: '2',
|
||||||
|
name: 'B',
|
||||||
|
},
|
||||||
|
{ ...variantTemplate, id: '3', name: 'C' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const weights = updateWeightEdit(variants, 1000).map(
|
||||||
|
(variant) => variant.weight,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(weights).toEqual([600, 600, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when sum of multiple items exceed 100%', () => {
|
||||||
|
const variants = [
|
||||||
|
{
|
||||||
|
...variantTemplate,
|
||||||
|
weightType: 'fix' as const,
|
||||||
|
weight: 400,
|
||||||
|
id: '1',
|
||||||
|
name: 'A',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...variantTemplate,
|
||||||
|
weightType: 'fix' as const,
|
||||||
|
weight: 450,
|
||||||
|
id: '2',
|
||||||
|
name: 'B',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...variantTemplate,
|
||||||
|
id: '3',
|
||||||
|
name: 'C',
|
||||||
|
},
|
||||||
|
{ ...variantTemplate, id: '4', name: 'D' },
|
||||||
|
{
|
||||||
|
...variantTemplate,
|
||||||
|
id: '5',
|
||||||
|
name: 'E',
|
||||||
|
weightType: 'fix' as const,
|
||||||
|
weight: 350,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const weights = updateWeightEdit(variants, 1000).map(
|
||||||
|
(variant) => variant.weight,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(weights).toEqual([400, 450, 0, 0, 350]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -143,7 +143,7 @@ export function updateWeightEdit(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const getPercentage = () =>
|
const getPercentage = () =>
|
||||||
Math.round(remainingPercentage / variableVariantCount);
|
Math.max(Math.round(remainingPercentage / variableVariantCount), 0);
|
||||||
|
|
||||||
return variants.map((variant) => {
|
return variants.map((variant) => {
|
||||||
if (variant.weightType !== weightTypes.FIX) {
|
if (variant.weightType !== weightTypes.FIX) {
|
||||||
|
@ -173,6 +173,7 @@ interface IVariantFormProps {
|
|||||||
error?: string;
|
error?: string;
|
||||||
disableOverrides?: boolean;
|
disableOverrides?: boolean;
|
||||||
decorationColor?: string;
|
decorationColor?: string;
|
||||||
|
weightsError?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VariantForm = ({
|
export const VariantForm = ({
|
||||||
@ -183,6 +184,7 @@ export const VariantForm = ({
|
|||||||
error,
|
error,
|
||||||
disableOverrides = false,
|
disableOverrides = false,
|
||||||
decorationColor,
|
decorationColor,
|
||||||
|
weightsError,
|
||||||
}: IVariantFormProps) => {
|
}: IVariantFormProps) => {
|
||||||
const [name, setName] = useState(variant.name);
|
const [name, setName] = useState(variant.name);
|
||||||
const [customPercentage, setCustomPercentage] = useState(
|
const [customPercentage, setCustomPercentage] = useState(
|
||||||
@ -333,6 +335,11 @@ export const VariantForm = ({
|
|||||||
}
|
}
|
||||||
}, [variant.weight]);
|
}, [variant.weight]);
|
||||||
|
|
||||||
|
const percentageError =
|
||||||
|
errors?.percentage || weightsError
|
||||||
|
? 'Total weight may not exceed 100%'
|
||||||
|
: '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledVariantForm data-testid='VARIANT'>
|
<StyledVariantForm data-testid='VARIANT'>
|
||||||
<StyledDecoration color={decorationColor} />
|
<StyledDecoration color={decorationColor} />
|
||||||
@ -394,8 +401,8 @@ export const VariantForm = ({
|
|||||||
data-testid='VARIANT_WEIGHT_INPUT'
|
data-testid='VARIANT_WEIGHT_INPUT'
|
||||||
type='number'
|
type='number'
|
||||||
label='Variant weight'
|
label='Variant weight'
|
||||||
error={Boolean(errors.percentage)}
|
error={Boolean(percentageError)}
|
||||||
errorText={errors.percentage}
|
errorText={percentageError}
|
||||||
value={percentage}
|
value={percentage}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onSetPercentage(e.target.value)
|
onSetPercentage(e.target.value)
|
||||||
|
@ -115,6 +115,12 @@ export const NewStrategyVariants: FC<{
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const variantWeightsError =
|
||||||
|
variantsEdit.reduce(
|
||||||
|
(acc, variant) => acc + (variant.weight || 0),
|
||||||
|
0,
|
||||||
|
) !== 1000;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledVariantsHeader>
|
<StyledVariantsHeader>
|
||||||
@ -178,6 +184,7 @@ export const NewStrategyVariants: FC<{
|
|||||||
i % theme.palette.variants.length
|
i % theme.palette.variants.length
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
weightsError={variantWeightsError}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</StyledVariantForms>
|
</StyledVariantForms>
|
||||||
@ -192,7 +199,10 @@ export const NewStrategyVariants: FC<{
|
|||||||
>
|
>
|
||||||
Add variant
|
Add variant
|
||||||
</PermissionButton>
|
</PermissionButton>
|
||||||
<SplitPreviewSlider variants={variantsEdit} />
|
<SplitPreviewSlider
|
||||||
|
variants={variantsEdit}
|
||||||
|
weightsError={variantWeightsError}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -72,9 +72,13 @@ const StyledTypographySubtitle = styled(Typography)(({ theme }) => ({
|
|||||||
|
|
||||||
interface ISplitPreviewSliderProps {
|
interface ISplitPreviewSliderProps {
|
||||||
variants: IFeatureVariant[];
|
variants: IFeatureVariant[];
|
||||||
|
weightsError?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SplitPreviewSlider = ({ variants }: ISplitPreviewSliderProps) => {
|
const SplitPreviewSlider = ({
|
||||||
|
variants,
|
||||||
|
weightsError,
|
||||||
|
}: ISplitPreviewSliderProps) => {
|
||||||
if (variants.length < 1) {
|
if (variants.length < 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -108,7 +112,12 @@ const SplitPreviewSlider = ({ variants }: ISplitPreviewSliderProps) => {
|
|||||||
{' '}
|
{' '}
|
||||||
<StyledSegment>
|
<StyledSegment>
|
||||||
<StyledSegmentTrack index={index} />
|
<StyledSegmentTrack index={index} />
|
||||||
<StyledTypographySubtitle variant='subtitle2'>
|
<StyledTypographySubtitle
|
||||||
|
variant='subtitle2'
|
||||||
|
color={
|
||||||
|
weightsError ? 'error' : 'inherit'
|
||||||
|
}
|
||||||
|
>
|
||||||
{value}%
|
{value}%
|
||||||
</StyledTypographySubtitle>
|
</StyledTypographySubtitle>
|
||||||
</StyledSegment>
|
</StyledSegment>
|
||||||
|
Loading…
Reference in New Issue
Block a user