1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-09 01:17:06 +02:00

feat: strategy variants on strategy overview (#4776)

Refactors the breakdown of feature variants per strategy on the
environment overview level:
This commit is contained in:
Fredrik Strand Oseberg 2023-09-21 14:28:45 +02:00 committed by GitHub
parent 5799d0c90f
commit 6884f9cdc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 170 additions and 37 deletions

View File

@ -18,7 +18,6 @@ export const TooltipResolver = ({
if (!title && !titleComponent) { if (!title && !titleComponent) {
return children; return children;
} }
if (variant === 'custom') { if (variant === 'custom') {
return ( return (
<HtmlTooltip {...rest} title={title || titleComponent} arrow> <HtmlTooltip {...rest} title={title || titleComponent} arrow>

View File

@ -12,6 +12,7 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMenu'; import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMenu';
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer'; import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
import MenuStrategyRemove from './MenuStrategyRemove/MenuStrategyRemove'; import MenuStrategyRemove from './MenuStrategyRemove/MenuStrategyRemove';
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';
interface IStrategyItemProps { interface IStrategyItemProps {
environmentId: string; environmentId: string;
@ -86,6 +87,9 @@ export const StrategyItem: FC<IStrategyItemProps> = ({
} }
> >
<StrategyExecution strategy={strategy} /> <StrategyExecution strategy={strategy} />
{strategy.variants ? (
<SplitPreviewSlider variants={strategy.variants} />
) : null}
</StrategyItemContainer> </StrategyItemContainer>
); );
}; };

View File

@ -1,10 +1,9 @@
import { Box, Typography, styled } from '@mui/material'; import { Box, Typography, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver';
import { IFeatureVariant } from 'interfaces/featureToggle';
type SplitPreviewSliderProps = { const StyledContainer = styled(Box)(() => ({
values: number[];
};
const StyledContainer = styled(Box)(({ theme }) => ({
display: 'flex', display: 'flex',
width: '100%', width: '100%',
position: 'relative', position: 'relative',
@ -18,55 +17,188 @@ const StyledTrack = styled(Box)(({ theme }) => ({
overflow: 'hidden', overflow: 'hidden',
})); }));
const StyledSegment = styled(Box)(({ theme }) => ({ const StyledSegment = styled(Box)(() => ({
height: '100%', height: '100%',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
width: '100%',
})); }));
const StyledSegmentTrack = styled(Box)(({ theme }) => ({ const StyledSegmentTrack = styled(Box, {
height: theme.spacing(3), shouldForwardProp: prop => prop !== 'index',
})<{ index: number }>(({ theme, index }) => ({
height: theme.spacing(1.8),
width: '100%', width: '100%',
position: 'relative', position: 'relative',
background: theme.palette.variants[index % theme.palette.variants.length],
})); }));
const SplitPreviewSlider = ({ values }: SplitPreviewSliderProps) => { const StyledHeaderContainer = styled(Box)(({ theme }) => ({
if (values.length < 2) { display: 'flex',
alignItems: 'center',
marginBottom: theme.spacing(1),
}));
const StyledTypography = styled(Typography)(({ theme }) => ({
marginY: theme.spacing(1),
}));
const StyledVariantBoxContainer = styled(Box)(() => ({
display: 'flex',
alignItems: 'center',
marginLeft: 'auto',
}));
const StyledVariantBox = styled(Box, {
shouldForwardProp: prop => prop !== 'index',
})<{ index: number }>(({ theme, index }) => ({
display: 'flex',
alignItems: 'center',
marginRight: theme.spacing(2),
'& div': {
width: theme.spacing(1.6),
height: theme.spacing(1.6),
borderRadius: '50%',
marginRight: theme.spacing(1),
background:
theme.palette.variants[index % theme.palette.variants.length],
},
}));
const StyledTypographySubtitle = styled(Typography)(({ theme }) => ({
marginTop: theme.spacing(1),
}));
interface ISplitPreviewSliderProps {
variants: IFeatureVariant[];
}
const SplitPreviewSlider = ({ variants }: ISplitPreviewSliderProps) => {
if (variants.length < 2) {
return null; return null;
} }
return ( return (
<Box sx={theme => ({ marginTop: theme.spacing(2) })}> <Box sx={theme => ({ marginTop: theme.spacing(2) })}>
<Typography <SplitPreviewHeader variants={variants} />
variant="body2"
sx={theme => ({ marginY: theme.spacing(1) })}
>
Split preview
</Typography>
<StyledContainer> <StyledContainer>
<StyledTrack /> <StyledTrack />
{values.map((value, index) => (
<StyledSegment key={index} sx={{ width: `${value}%` }}> {variants.map((variant, index) => {
<StyledSegmentTrack const value = variant.weight / 10;
sx={theme => ({ return (
background: <TooltipResolver
theme.palette.variants[ variant="custom"
index % theme.palette.variants.length key={index}
], arrow
})} onClick={e => e.preventDefault()}
titleComponent={
<SplitPreviewTooltip
variant={variant}
index={index}
/> />
<Typography }
variant="subtitle2"
sx={theme => ({ marginTop: theme.spacing(1) })}
> >
<Box
style={{
width: `${value}%`,
}}
>
{' '}
<StyledSegment>
<StyledSegmentTrack index={index} />
<StyledTypographySubtitle variant="subtitle2">
{value}% {value}%
</Typography> </StyledTypographySubtitle>
</StyledSegment> </StyledSegment>
))} </Box>
</TooltipResolver>
);
})}
</StyledContainer> </StyledContainer>
</Box> </Box>
); );
}; };
const SplitPreviewHeader = ({ variants }: ISplitPreviewSliderProps) => {
return (
<StyledHeaderContainer>
<StyledTypography variant="body2">
Feature variants ({variants.length})
</StyledTypography>
<StyledVariantBoxContainer>
{variants.map((variant, index) => (
<StyledVariantBox key={index} index={index}>
<Box />
<StyledTypography variant="body2">
{variant.name}
</StyledTypography>
</StyledVariantBox>
))}
</StyledVariantBoxContainer>
</StyledHeaderContainer>
);
};
interface ISplitPreviewTooltip {
variant: IFeatureVariant;
index: number;
}
const StyledTooltipContainer = styled(Box)(() => ({
display: 'flex',
flexDirection: 'column',
}));
const StyledVariantContainer = styled(Box)(() => ({
display: 'flex',
alignItems: 'center',
minWidth: '250px',
}));
const StyledPayloadContainer = styled(Box)(({ theme }) => ({
marginTop: theme.spacing(1),
display: 'flex',
flexDirection: 'column',
}));
const StyledPayloadLabel = styled(Typography)(({ theme }) => ({
marginBottom: theme.spacing(1),
}));
const SplitPreviewTooltip = ({ variant, index }: ISplitPreviewTooltip) => {
return (
<StyledTooltipContainer>
<StyledVariantContainer>
<StyledVariantBox index={index}>
<Box />
</StyledVariantBox>
<Typography variant="subtitle2">
{variant.weight / 10}% - {variant.name}
</Typography>
</StyledVariantContainer>
{variant.payload ? (
<StyledPayloadContainer>
<StyledPayloadLabel variant="body2">
Payload
</StyledPayloadLabel>
<ConditionallyRender
condition={variant.payload.type === 'json'}
show={<code>{variant.payload.value}</code>}
elseShow={
<Typography variant="body2">
{variant.payload.value}
</Typography>
}
/>
</StyledPayloadContainer>
) : null}
</StyledTooltipContainer>
);
};
export default SplitPreviewSlider; export default SplitPreviewSlider;

View File

@ -157,9 +157,7 @@ export const StrategyVariants: FC<{
> >
Add variant Add variant
</PermissionButton> </PermissionButton>
<SplitPreviewSlider <SplitPreviewSlider variants={variantsEdit} />
values={variantsEdit.map(variant => variant.weight / 10)}
/>
</> </>
); );
}; };