Skip to content

Commit

Permalink
feat: add variants to release plan template strategies (#8870)
Browse files Browse the repository at this point in the history
  • Loading branch information
daveleek authored Nov 27, 2024
1 parent f629773 commit 9044d4c
Show file tree
Hide file tree
Showing 2 changed files with 233 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import type { IReleasePlanMilestoneStrategy } from 'interfaces/releasePlans';
import { useEffect, useState } from 'react';
import { Box, styled, Typography, Button } from '@mui/material';
import { HelpIcon } from '../../common/HelpIcon/HelpIcon';
import { StrategyVariantsUpgradeAlert } from 'component/common/StrategyVariantsUpgradeAlert/StrategyVariantsUpgradeAlert';
import { VariantForm } from 'component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantForm';
import { v4 as uuidv4 } from 'uuid';
import type { IFeatureVariantEdit } from 'component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/EnvironmentVariantsModal';
import { updateWeightEdit } from 'component/common/util';
import { WeightType } from 'constants/variantTypes';
import { useTheme } from '@mui/material';
import Add from '@mui/icons-material/Add';
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';

const StyledVariantForms = styled('div')({
display: 'flex',
flexDirection: 'column',
});

const StyledHelpIconBox = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
}));

const StyledVariantsHeader = styled('div')(({ theme }) => ({
color: theme.palette.text.secondary,
marginTop: theme.spacing(1.5),
}));

interface IMilestoneStrategyVariantsProps {
strategy: Omit<IReleasePlanMilestoneStrategy, 'milestoneId'>;
setStrategy: React.Dispatch<
React.SetStateAction<Omit<IReleasePlanMilestoneStrategy, 'milestoneId'>>
>;
}

export const MilestoneStrategyVariants = ({
strategy,
setStrategy,
}: IMilestoneStrategyVariantsProps) => {
const initialVariants = (strategy.variants || []).map((variant) => ({
...variant,
new: true,
isValid: true,
id: uuidv4(),
overrides: [],
}));
const [variantsEdit, setVariantsEdit] =
useState<IFeatureVariantEdit[]>(initialVariants);

const stickiness =
strategy?.parameters && 'stickiness' in strategy?.parameters
? String(strategy.parameters.stickiness)
: 'default';
const theme = useTheme();

useEffect(() => {
return () => {
setStrategy((prev) => ({
...prev,
variants: variantsEdit.filter((variant) =>
Boolean(variant.name),
),
}));
};
}, [JSON.stringify(variantsEdit)]);

useEffect(() => {
setStrategy((prev) => ({
...prev,
variants: variantsEdit.map((variant) => ({
stickiness,
name: variant.name,
weight: variant.weight,
payload: variant.payload,
weightType: variant.weightType,
})),
}));
}, [stickiness, JSON.stringify(variantsEdit)]);

const updateVariant = (updatedVariant: IFeatureVariantEdit, id: string) => {
setVariantsEdit((prevVariants) =>
updateWeightEdit(
prevVariants.map((prevVariant) =>
prevVariant.id === id ? updatedVariant : prevVariant,
),
1000,
),
);
};

const addVariant = () => {
const id = uuidv4();
setVariantsEdit((variantsEdit) => [
...variantsEdit,
{
name: '',
weightType: WeightType.VARIABLE,
weight: 0,
stickiness,
new: true,
isValid: false,
id,
},
]);
};

const variantWeightsError =
variantsEdit.reduce(
(acc, variant) => acc + (variant.weight || 0),
0,
) !== 1000;

return (
<>
<StyledVariantsHeader>
Variants enhance a feature flag by providing a version of the
feature to be enabled
</StyledVariantsHeader>
<StyledHelpIconBox>
<Typography>Variants</Typography>
<HelpIcon
htmlTooltip
tooltip={
<Box>
<Typography variant='body2'>
Variants in feature toggling allow you to serve
different versions of a feature to different
users. This can be used for A/B testing, gradual
rollouts, and canary releases. Variants provide
a way to control the user experience at a
granular level, enabling you to test and
optimize different aspects of your features.
Read more about variants{' '}
<a
href='https://docs.getunleash.io/reference/strategy-variants'
target='_blank'
rel='noopener noreferrer'
>
here
</a>
</Typography>
</Box>
}
/>
</StyledHelpIconBox>
<StyledVariantForms>
{variantsEdit.length > 0 && <StrategyVariantsUpgradeAlert />}

{variantsEdit.map((variant, i) => (
<VariantForm
disableOverrides={true}
key={variant.id}
variant={variant}
variants={variantsEdit}
updateVariant={(updatedVariant) =>
updateVariant(updatedVariant, variant.id)
}
removeVariant={() =>
setVariantsEdit((variantsEdit) =>
updateWeightEdit(
variantsEdit.filter(
(v) => v.id !== variant.id,
),
1000,
),
)
}
decorationColor={
theme.palette.variants[
i % theme.palette.variants.length
]
}
weightsError={variantWeightsError}
/>
))}
</StyledVariantForms>
<Button onClick={addVariant} variant='outlined' startIcon={<Add />}>
Add variant
</Button>
<SplitPreviewSlider
variants={variantsEdit}
weightsError={variantWeightsError}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Badge } from 'component/common/Badge/Badge';
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import type { IReleasePlanMilestoneStrategy } from 'interfaces/releasePlans';
import type { ISegment } from 'interfaces/segment';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { BuiltInStrategies, formatStrategyName } from 'utils/strategyNames';
import { MilestoneStrategyTitle } from './MilestoneStrategyTitle';
import { MilestoneStrategyType } from './MilestoneStrategyType';
Expand All @@ -22,6 +22,7 @@ import { useFormErrors } from 'hooks/useFormErrors';
import produce from 'immer';
import { MilestoneStrategySegment } from './MilestoneStrategySegment';
import { MilestoneStrategyConstraints } from './MilestoneStrategyConstraints';
import { MilestoneStrategyVariants } from './MilestoneStrategyVariants';
import { useConstraintsValidation } from 'hooks/api/getters/useConstraintsValidation/useConstraintsValidation';

const StyledCancelButton = styled(Button)(({ theme }) => ({
Expand Down Expand Up @@ -138,6 +139,28 @@ export const ReleasePlanTemplateAddStrategyForm = ({
const { strategyDefinition } = useStrategy(strategy?.name);
const hasValidConstraints = useConstraintsValidation(strategy?.constraints);
const errors = useFormErrors();
const showVariants = Boolean(
addStrategy?.parameters && 'stickiness' in addStrategy?.parameters,
);

const stickiness =
addStrategy?.parameters && 'stickiness' in addStrategy?.parameters
? String(addStrategy.parameters.stickiness)
: 'default';

useEffect(() => {
setAddStrategy((prev) => ({
...prev,
variants: (addStrategy.variants || []).map((variant) => ({
stickiness,
name: variant.name,
weight: variant.weight,
payload: variant.payload,
weightType: variant.weightType,
})),
}));
}, [stickiness, JSON.stringify(addStrategy.variants)]);

if (!strategy || !addStrategy || !strategyDefinition) {
return null;
}
Expand All @@ -153,6 +176,7 @@ export const ReleasePlanTemplateAddStrategyForm = ({
return constraintCount + segmentCount;
};

const validateParameter = (key: string, value: string) => true;
const updateParameter = (name: string, value: string) => {
setAddStrategy(
produce((draft) => {
Expand All @@ -165,6 +189,7 @@ export const ReleasePlanTemplateAddStrategyForm = ({
}
draft.parameters = draft.parameters ?? {};
draft.parameters[name] = value;
validateParameter(name, value);
}),
);
};
Expand Down Expand Up @@ -220,6 +245,18 @@ export const ReleasePlanTemplateAddStrategyForm = ({
</Typography>
}
/>
{showVariants && (
<Tab
label={
<Typography>
Variants
<StyledBadge>
{addStrategy?.variants?.length || 0}
</StyledBadge>
</Typography>
}
/>
)}
</StyledTabs>
<StyledContentDiv>
{activeTab === 0 && (
Expand Down Expand Up @@ -262,6 +299,12 @@ export const ReleasePlanTemplateAddStrategyForm = ({
</StyledTargetingHeader>
</>
)}
{activeTab === 2 && showVariants && (
<MilestoneStrategyVariants
strategy={addStrategy}
setStrategy={setAddStrategy}
/>
)}
</StyledContentDiv>
<StyledButtonContainer>
<Button
Expand Down

0 comments on commit 9044d4c

Please sign in to comment.