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

Feat/new strategy variants tab (#5649)

This PR sets up the variants tab for the new strategy configuration.
Also some minor adjustments to the new form:

* Change where padding is controlled to allow us to have more granular
control over how the buttons width and border should look and have the
tabs have full-width borders across the form
* Move the buttons to be absolutely positioned at the bottom of the page
* Move where we display banners to avoid clutter

<img width="1284" alt="Skjermbilde 2023-12-14 kl 21 17 53"
src="https://github.com/Unleash/unleash/assets/16081982/45e6a364-e4aa-47ac-b420-f3be9b39a15e">
This commit is contained in:
Fredrik Strand Oseberg 2023-12-15 12:26:13 +01:00 committed by GitHub
parent 848415c5ca
commit cbd6aa1324
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 310 additions and 173 deletions

View File

@ -1,15 +1,6 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Alert,
Button,
styled,
Tabs,
Tab,
Typography,
Divider,
Box,
} from '@mui/material';
import { Alert, Button, styled, Tabs, Tab, Box, Divider } from '@mui/material';
import {
IFeatureStrategy,
IFeatureStrategyParameters,
@ -42,6 +33,7 @@ import { FeatureStrategyTitle } from './FeatureStrategyTitle/FeatureStrategyTitl
import { FeatureStrategyEnabledDisabled } from './FeatureStrategyEnabledDisabled/FeatureStrategyEnabledDisabled';
import { StrategyVariants } from 'component/feature/StrategyTypes/StrategyVariants';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { formatStrategyName } from 'utils/strategyNames';
interface IFeatureStrategyFormProps {
feature: IFeatureToggle;
@ -77,8 +69,15 @@ const StyledDividerContent = styled(Box)(({ theme }) => ({
}));
const StyledForm = styled('form')(({ theme }) => ({
display: 'grid',
position: 'relative',
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(2),
padding: theme.spacing(6),
paddingBottom: theme.spacing(12),
paddingTop: theme.spacing(4),
overflow: 'auto',
height: '100%',
}));
const StyledHr = styled('hr')(({ theme }) => ({
@ -89,11 +88,31 @@ const StyledHr = styled('hr')(({ theme }) => ({
background: theme.palette.background.elevation2,
}));
const StyledButtons = styled('div')(({ theme }) => ({
const StyledTitle = styled('h1')(({ theme }) => ({
fontWeight: 'normal',
display: 'flex',
alignItems: 'center',
}));
const StyledButtons = styled('div')(({ theme }) => ({
bottom: 0,
right: 0,
left: 0,
position: 'absolute',
display: 'flex',
padding: theme.spacing(3),
paddingRight: theme.spacing(6),
paddingLeft: theme.spacing(6),
backgroundColor: theme.palette.common.white,
justifyContent: 'end',
gap: theme.spacing(2),
paddingBottom: theme.spacing(10),
borderTop: `1px solid ${theme.palette.divider}`,
}));
const StyledTabs = styled(Tabs)(({ theme }) => ({
borderTop: `1px solid ${theme.palette.divider}`,
borderBottom: `1px solid ${theme.palette.divider}`,
paddingLeft: theme.spacing(6),
paddingRight: theme.spacing(6),
}));
const StyledBox = styled(Box)(({ theme }) => ({
@ -111,6 +130,15 @@ const StyledTargetingHeader = styled('div')(({ theme }) => ({
marginTop: theme.spacing(1.5),
}));
const StyledHeaderBox = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
paddingLeft: theme.spacing(6),
paddingRight: theme.spacing(6),
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2),
}));
export const NewFeatureStrategyForm = ({
projectId,
feature,
@ -225,58 +253,22 @@ export const NewFeatureStrategyForm = ({
};
return (
<StyledForm onSubmit={onSubmitWithValidation}>
<Tabs value={tab} onChange={handleChange}>
<>
<StyledHeaderBox>
<StyledTitle>
{formatStrategyName(strategy.name || '')}
</StyledTitle>
</StyledHeaderBox>
<StyledTabs value={tab} onChange={handleChange}>
<Tab label='General' />
<Tab label='Targeting' />
<Tab label='Variants' />
</Tabs>
</StyledTabs>
<StyledForm onSubmit={onSubmitWithValidation}>
<ConditionallyRender
condition={tab === 0}
show={
<>
<ConditionallyRender
condition={hasChangeRequestInReviewForEnvironment}
show={alert}
elseShow={
<ConditionallyRender
condition={Boolean(isChangeRequest)}
show={
<FeatureStrategyChangeRequestAlert
environment={environmentId}
/>
}
/>
}
/>
<FeatureStrategyEnabled
projectId={feature.project}
featureId={feature.name}
environmentId={environmentId}
>
<ConditionallyRender
condition={Boolean(isChangeRequest)}
show={
<Alert severity='success'>
This feature toggle is currently enabled
in the <strong>{environmentId}</strong>{' '}
environment. Any changes made here will
be available to users as soon as these
changes are approved and applied.
</Alert>
}
elseShow={
<Alert severity='success'>
This feature toggle is currently enabled
in the <strong>{environmentId}</strong>{' '}
environment. Any changes made here will
be available to users as soon as you hit{' '}
<strong>save</strong>.
</Alert>
}
/>
</FeatureStrategyEnabled>
<StyledHr />
<FeatureStrategyTitle
title={strategy.title || ''}
setTitle={(title) => {
@ -303,6 +295,54 @@ export const NewFeatureStrategyForm = ({
errors={errors}
hasAccess={access}
/>
<ConditionallyRender
condition={
hasChangeRequestInReviewForEnvironment
}
show={alert}
elseShow={
<ConditionallyRender
condition={Boolean(isChangeRequest)}
show={
<FeatureStrategyChangeRequestAlert
environment={environmentId}
/>
}
/>
}
/>
<FeatureStrategyEnabled
projectId={feature.project}
featureId={feature.name}
environmentId={environmentId}
>
<ConditionallyRender
condition={Boolean(isChangeRequest)}
show={
<Alert severity='success'>
This feature toggle is currently
enabled in the{' '}
<strong>{environmentId}</strong>{' '}
environment. Any changes made here
will be available to users as soon
as these changes are approved and
applied.
</Alert>
}
elseShow={
<Alert severity='success'>
This feature toggle is currently
enabled in the{' '}
<strong>{environmentId}</strong>{' '}
environment. Any changes made here
will be available to users as soon
as you hit <strong>save</strong>.
</Alert>
}
/>
</FeatureStrategyEnabled>
</>
}
/>
@ -313,9 +353,9 @@ export const NewFeatureStrategyForm = ({
<>
<StyledTargetingHeader>
Segmentation and constraints allow you to set
filters on your strategies, so that they will only
be evaluated for users and applications that match
the specified preconditions.
filters on your strategies, so that they will
only be evaluated for users and applications
that match the specified preconditions.
</StyledTargetingHeader>
<FeatureStrategySegment
segments={segments}
@ -393,5 +433,6 @@ export const NewFeatureStrategyForm = ({
/>
</StyledButtons>
</StyledForm>
</>
);
};

View File

@ -180,10 +180,10 @@ export const NewFeatureStrategyCreate = () => {
return (
<FormTemplate
modal
title={formatStrategyName(strategyName)}
description={featureStrategyHelp}
documentationLink={featureStrategyDocsLink}
documentationLinkLabel={featureStrategyDocsLinkLabel}
disablePadding
formatApiCode={() =>
formatAddStrategyApiCode(
projectId,

View File

@ -200,7 +200,7 @@ export const NewFeatureStrategyEdit = () => {
return (
<FormTemplate
modal
title={formatStrategyName(strategy.name ?? '')}
disablePadding
description={featureStrategyHelp}
documentationLink={featureStrategyDocsLink}
documentationLinkLabel={featureStrategyDocsLinkLabel}

View File

@ -6,18 +6,32 @@ import PermissionButton from '../../common/PermissionButton/PermissionButton';
import { UPDATE_FEATURE_ENVIRONMENT_VARIANTS } from '../../providers/AccessProvider/permissions';
import { v4 as uuidv4 } from 'uuid';
import { WeightType } from '../../../constants/variantTypes';
import { Link, styled, Typography, useTheme } from '@mui/material';
import { Box, Link, styled, Typography, useTheme } from '@mui/material';
import { IFeatureStrategy } from 'interfaces/strategy';
import SplitPreviewSlider from './SplitPreviewSlider/SplitPreviewSlider';
import { HelpIcon } from '../../common/HelpIcon/HelpIcon';
import { StrategyVariantsUpgradeAlert } from '../../common/StrategyVariantsUpgradeAlert/StrategyVariantsUpgradeAlert';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useUiFlag } from 'hooks/useUiFlag';
import { Add } from '@mui/icons-material';
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),
}));
export const StrategyVariants: FC<{
setStrategy: React.Dispatch<
React.SetStateAction<Partial<IFeatureStrategy>>
@ -29,6 +43,8 @@ export const StrategyVariants: FC<{
const { trackEvent } = usePlausibleTracker();
const [variantsEdit, setVariantsEdit] = useState<IFeatureVariantEdit[]>([]);
const theme = useTheme();
const newStrategyConfiguration = useUiFlag('newStrategyConfiguration');
const stickiness =
strategy?.parameters && 'stickiness' in strategy?.parameters
? String(strategy.parameters.stickiness)
@ -91,6 +107,86 @@ export const StrategyVariants: FC<{
});
};
if (newStrategyConfiguration) {
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>
<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
]
}
/>
))}
</StyledVariantForms>
<PermissionButton
onClick={addVariant}
variant='outlined'
permission={UPDATE_FEATURE_ENVIRONMENT_VARIANTS}
projectId={projectId}
environmentId={environment}
data-testid='ADD_STRATEGY_VARIANT_BUTTON'
startIcon={<Add />}
>
Add variant
</PermissionButton>
<SplitPreviewSlider variants={variantsEdit} />
</>
);
}
return (
<>
<Typography