1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-31 13:47:02 +02:00

Feat: strategy variant slider (#4344)

## About the changes

![image](https://github.com/Unleash/unleash/assets/2625371/835cdb1f-c0ad-4966-81a3-9e35944ee1ae)
This commit is contained in:
Tymoteusz Czech 2023-07-26 11:36:16 +02:00 committed by GitHub
parent 2b565aeef7
commit 909831db6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 121 additions and 2 deletions

View File

@ -4,6 +4,7 @@ import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
import SelectMenu from 'component/common/select'; import SelectMenu from 'component/common/select';
import { OverrideConfig } from 'component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantOverrides/VariantOverrides'; import { OverrideConfig } from 'component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsModal/VariantForm/VariantOverrides/VariantOverrides';
import { import {
Box,
Button, Button,
FormControlLabel, FormControlLabel,
IconButton, IconButton,
@ -28,8 +29,20 @@ const StyledVariantForm = styled('div')(({ theme }) => ({
padding: theme.spacing(3), padding: theme.spacing(3),
marginBottom: theme.spacing(3), marginBottom: theme.spacing(3),
borderRadius: theme.shape.borderRadiusLarge, borderRadius: theme.shape.borderRadiusLarge,
overflow: 'hidden',
})); }));
const StyledDecoration = styled('div')<{ color?: string }>(
({ theme, color }) => ({
position: 'absolute',
left: 0,
top: 0,
height: '100%',
background: color || 'transparent',
width: theme.spacing(1),
})
);
const StyledDeleteButtonTooltip = styled(Tooltip)(({ theme }) => ({ const StyledDeleteButtonTooltip = styled(Tooltip)(({ theme }) => ({
position: 'absolute', position: 'absolute',
top: theme.spacing(2), top: theme.spacing(2),
@ -151,6 +164,7 @@ interface IVariantFormProps {
removeVariant: (variantId: string) => void; removeVariant: (variantId: string) => void;
error?: string; error?: string;
disableOverrides?: boolean; disableOverrides?: boolean;
decorationColor?: string;
} }
export const VariantForm = ({ export const VariantForm = ({
@ -160,6 +174,7 @@ export const VariantForm = ({
removeVariant, removeVariant,
error, error,
disableOverrides = false, disableOverrides = false,
decorationColor,
}: IVariantFormProps) => { }: IVariantFormProps) => {
const [name, setName] = useState(variant.name); const [name, setName] = useState(variant.name);
const [customPercentage, setCustomPercentage] = useState( const [customPercentage, setCustomPercentage] = useState(
@ -306,6 +321,7 @@ export const VariantForm = ({
return ( return (
<StyledVariantForm data-testid="VARIANT"> <StyledVariantForm data-testid="VARIANT">
<StyledDecoration color={decorationColor} />
<StyledDeleteButtonTooltip <StyledDeleteButtonTooltip
arrow arrow
title={ title={

View File

@ -0,0 +1,72 @@
import { Box, Typography, styled } from '@mui/material';
type SplitPreviewSliderProps = {
values: number[];
};
const StyledContainer = styled(Box)(({ theme }) => ({
display: 'flex',
width: '100%',
position: 'relative',
}));
const StyledTrack = styled(Box)(({ theme }) => ({
position: 'absolute',
height: theme.spacing(3),
width: '100%',
display: 'flex',
overflow: 'hidden',
}));
const StyledSegment = styled(Box)(({ theme }) => ({
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}));
const StyledSegmentTrack = styled(Box)(({ theme }) => ({
height: theme.spacing(3),
width: '100%',
position: 'relative',
}));
const SplitPreviewSlider = ({ values }: SplitPreviewSliderProps) => {
if (values.length < 2) {
return null;
}
return (
<Box sx={theme => ({ marginTop: theme.spacing(2) })}>
<Typography
variant="body2"
sx={theme => ({ marginY: theme.spacing(1) })}
>
Split preview
</Typography>
<StyledContainer>
<StyledTrack />
{values.map((value, index) => (
<StyledSegment key={index} sx={{ width: `${value}%` }}>
<StyledSegmentTrack
sx={theme => ({
background:
theme.palette.variants[
index % theme.palette.variants.length
],
})}
/>
<Typography
variant="subtitle2"
sx={theme => ({ marginTop: theme.spacing(1) })}
>
{value}%
</Typography>
</StyledSegment>
))}
</StyledContainer>
</Box>
);
};
export default SplitPreviewSlider;

View File

@ -7,9 +7,10 @@ import { UPDATE_FEATURE_ENVIRONMENT_VARIANTS } from '../../providers/AccessProvi
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { WeightType } from '../../../constants/variantTypes'; import { WeightType } from '../../../constants/variantTypes';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { styled, Typography } from '@mui/material'; import { styled, Typography, useTheme } from '@mui/material';
import { useRequiredQueryParam } from 'hooks/useRequiredQueryParam'; import { useRequiredQueryParam } from 'hooks/useRequiredQueryParam';
import { IFeatureStrategy } from 'interfaces/strategy'; import { IFeatureStrategy } from 'interfaces/strategy';
import SplitPreviewSlider from './SplitPreviewSlider/SplitPreviewSlider';
const StyledVariantForms = styled('div')({ const StyledVariantForms = styled('div')({
display: 'flex', display: 'flex',
@ -25,6 +26,7 @@ export const StrategyVariants: FC<{
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
const environment = useRequiredQueryParam('environmentId'); const environment = useRequiredQueryParam('environmentId');
const [variantsEdit, setVariantsEdit] = useState<IFeatureVariantEdit[]>([]); const [variantsEdit, setVariantsEdit] = useState<IFeatureVariantEdit[]>([]);
const theme = useTheme();
const stickiness = const stickiness =
strategy?.parameters && 'stickiness' in strategy?.parameters strategy?.parameters && 'stickiness' in strategy?.parameters
? String(strategy.parameters.stickiness) ? String(strategy.parameters.stickiness)
@ -88,7 +90,7 @@ export const StrategyVariants: FC<{
Variants Variants
</Typography> </Typography>
<StyledVariantForms> <StyledVariantForms>
{variantsEdit.map(variant => ( {variantsEdit.map((variant, i) => (
<VariantForm <VariantForm
disableOverrides={true} disableOverrides={true}
key={variant.id} key={variant.id}
@ -107,6 +109,11 @@ export const StrategyVariants: FC<{
) )
) )
} }
decorationColor={
theme.palette.variants[
i % theme.palette.variants.length
]
}
/> />
))} ))}
</StyledVariantForms> </StyledVariantForms>
@ -119,6 +126,9 @@ export const StrategyVariants: FC<{
> >
Add variant Add variant
</PermissionButton> </PermissionButton>
<SplitPreviewSlider
values={variantsEdit.map(variant => variant.weight / 10)}
/>
</> </>
); );
}; };

View File

@ -94,4 +94,17 @@ export const colors = {
600: '#1f3751', 600: '#1f3751',
500: '#0e2840', 500: '#0e2840',
}, },
variants: [
'#BEBBF3',
'#FFC46F',
'#B0D182',
'#96D2FA',
'#F7E3AE',
'#7FBAA9',
'#D3B9DB',
'#FBC5A0',
'#DDE7B5',
'#9EC4E3',
'#F8B6CC',
] as string[],
} as const; } as const;

View File

@ -1,6 +1,7 @@
import { createTheme } from '@mui/material/styles'; import { createTheme } from '@mui/material/styles';
import { alpha } from '@mui/material'; import { alpha } from '@mui/material';
import { focusable } from 'themes/themeStyles'; import { focusable } from 'themes/themeStyles';
import { colors } from './colors';
const actionColors = { const actionColors = {
0.54: 'rgba(223, 222, 255, 0.54)', 0.54: 'rgba(223, 222, 255, 0.54)',
@ -271,6 +272,7 @@ const theme = {
// A400: '#A6000E', // A400: '#A6000E',
// A700: '#A6000E', // A700: '#A6000E',
}, },
variants: colors.variants,
}, },
}; };

View File

@ -257,6 +257,7 @@ const theme = {
// A400: '#A6000E', // A400: '#A6000E',
// A700: '#A6000E', // A700: '#A6000E',
}, },
variants: colors.variants,
}, },
}; };

View File

@ -112,6 +112,11 @@ declare module '@mui/material/styles' {
disabled: string; disabled: string;
expanded: string; expanded: string;
}; };
/**
* Variants, percentage split in strategies
**/
variants: string[];
} }
interface Theme extends CustomTheme {} interface Theme extends CustomTheme {}