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:
parent
848415c5ca
commit
cbd6aa1324
@ -1,15 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import {
|
import { Alert, Button, styled, Tabs, Tab, Box, Divider } from '@mui/material';
|
||||||
Alert,
|
|
||||||
Button,
|
|
||||||
styled,
|
|
||||||
Tabs,
|
|
||||||
Tab,
|
|
||||||
Typography,
|
|
||||||
Divider,
|
|
||||||
Box,
|
|
||||||
} from '@mui/material';
|
|
||||||
import {
|
import {
|
||||||
IFeatureStrategy,
|
IFeatureStrategy,
|
||||||
IFeatureStrategyParameters,
|
IFeatureStrategyParameters,
|
||||||
@ -42,6 +33,7 @@ import { FeatureStrategyTitle } from './FeatureStrategyTitle/FeatureStrategyTitl
|
|||||||
import { FeatureStrategyEnabledDisabled } from './FeatureStrategyEnabledDisabled/FeatureStrategyEnabledDisabled';
|
import { FeatureStrategyEnabledDisabled } from './FeatureStrategyEnabledDisabled/FeatureStrategyEnabledDisabled';
|
||||||
import { StrategyVariants } from 'component/feature/StrategyTypes/StrategyVariants';
|
import { StrategyVariants } from 'component/feature/StrategyTypes/StrategyVariants';
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
|
import { formatStrategyName } from 'utils/strategyNames';
|
||||||
|
|
||||||
interface IFeatureStrategyFormProps {
|
interface IFeatureStrategyFormProps {
|
||||||
feature: IFeatureToggle;
|
feature: IFeatureToggle;
|
||||||
@ -77,8 +69,15 @@ const StyledDividerContent = styled(Box)(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledForm = styled('form')(({ theme }) => ({
|
const StyledForm = styled('form')(({ theme }) => ({
|
||||||
display: 'grid',
|
position: 'relative',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
gap: theme.spacing(2),
|
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 }) => ({
|
const StyledHr = styled('hr')(({ theme }) => ({
|
||||||
@ -89,11 +88,31 @@ const StyledHr = styled('hr')(({ theme }) => ({
|
|||||||
background: theme.palette.background.elevation2,
|
background: theme.palette.background.elevation2,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledButtons = styled('div')(({ theme }) => ({
|
const StyledTitle = styled('h1')(({ theme }) => ({
|
||||||
|
fontWeight: 'normal',
|
||||||
display: 'flex',
|
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',
|
justifyContent: 'end',
|
||||||
gap: theme.spacing(2),
|
borderTop: `1px solid ${theme.palette.divider}`,
|
||||||
paddingBottom: theme.spacing(10),
|
}));
|
||||||
|
|
||||||
|
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 }) => ({
|
const StyledBox = styled(Box)(({ theme }) => ({
|
||||||
@ -111,6 +130,15 @@ const StyledTargetingHeader = styled('div')(({ theme }) => ({
|
|||||||
marginTop: theme.spacing(1.5),
|
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 = ({
|
export const NewFeatureStrategyForm = ({
|
||||||
projectId,
|
projectId,
|
||||||
feature,
|
feature,
|
||||||
@ -225,58 +253,22 @@ export const NewFeatureStrategyForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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='General' />
|
||||||
<Tab label='Targeting' />
|
<Tab label='Targeting' />
|
||||||
<Tab label='Variants' />
|
<Tab label='Variants' />
|
||||||
</Tabs>
|
</StyledTabs>
|
||||||
|
<StyledForm onSubmit={onSubmitWithValidation}>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={tab === 0}
|
condition={tab === 0}
|
||||||
show={
|
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
|
<FeatureStrategyTitle
|
||||||
title={strategy.title || ''}
|
title={strategy.title || ''}
|
||||||
setTitle={(title) => {
|
setTitle={(title) => {
|
||||||
@ -303,6 +295,54 @@ export const NewFeatureStrategyForm = ({
|
|||||||
errors={errors}
|
errors={errors}
|
||||||
hasAccess={access}
|
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>
|
<StyledTargetingHeader>
|
||||||
Segmentation and constraints allow you to set
|
Segmentation and constraints allow you to set
|
||||||
filters on your strategies, so that they will only
|
filters on your strategies, so that they will
|
||||||
be evaluated for users and applications that match
|
only be evaluated for users and applications
|
||||||
the specified preconditions.
|
that match the specified preconditions.
|
||||||
</StyledTargetingHeader>
|
</StyledTargetingHeader>
|
||||||
<FeatureStrategySegment
|
<FeatureStrategySegment
|
||||||
segments={segments}
|
segments={segments}
|
||||||
@ -393,5 +433,6 @@ export const NewFeatureStrategyForm = ({
|
|||||||
/>
|
/>
|
||||||
</StyledButtons>
|
</StyledButtons>
|
||||||
</StyledForm>
|
</StyledForm>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -180,10 +180,10 @@ export const NewFeatureStrategyCreate = () => {
|
|||||||
return (
|
return (
|
||||||
<FormTemplate
|
<FormTemplate
|
||||||
modal
|
modal
|
||||||
title={formatStrategyName(strategyName)}
|
|
||||||
description={featureStrategyHelp}
|
description={featureStrategyHelp}
|
||||||
documentationLink={featureStrategyDocsLink}
|
documentationLink={featureStrategyDocsLink}
|
||||||
documentationLinkLabel={featureStrategyDocsLinkLabel}
|
documentationLinkLabel={featureStrategyDocsLinkLabel}
|
||||||
|
disablePadding
|
||||||
formatApiCode={() =>
|
formatApiCode={() =>
|
||||||
formatAddStrategyApiCode(
|
formatAddStrategyApiCode(
|
||||||
projectId,
|
projectId,
|
||||||
|
@ -200,7 +200,7 @@ export const NewFeatureStrategyEdit = () => {
|
|||||||
return (
|
return (
|
||||||
<FormTemplate
|
<FormTemplate
|
||||||
modal
|
modal
|
||||||
title={formatStrategyName(strategy.name ?? '')}
|
disablePadding
|
||||||
description={featureStrategyHelp}
|
description={featureStrategyHelp}
|
||||||
documentationLink={featureStrategyDocsLink}
|
documentationLink={featureStrategyDocsLink}
|
||||||
documentationLinkLabel={featureStrategyDocsLinkLabel}
|
documentationLinkLabel={featureStrategyDocsLinkLabel}
|
||||||
|
@ -6,18 +6,32 @@ import PermissionButton from '../../common/PermissionButton/PermissionButton';
|
|||||||
import { UPDATE_FEATURE_ENVIRONMENT_VARIANTS } from '../../providers/AccessProvider/permissions';
|
import { UPDATE_FEATURE_ENVIRONMENT_VARIANTS } from '../../providers/AccessProvider/permissions';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { WeightType } from '../../../constants/variantTypes';
|
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 { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
import SplitPreviewSlider from './SplitPreviewSlider/SplitPreviewSlider';
|
import SplitPreviewSlider from './SplitPreviewSlider/SplitPreviewSlider';
|
||||||
import { HelpIcon } from '../../common/HelpIcon/HelpIcon';
|
import { HelpIcon } from '../../common/HelpIcon/HelpIcon';
|
||||||
import { StrategyVariantsUpgradeAlert } from '../../common/StrategyVariantsUpgradeAlert/StrategyVariantsUpgradeAlert';
|
import { StrategyVariantsUpgradeAlert } from '../../common/StrategyVariantsUpgradeAlert/StrategyVariantsUpgradeAlert';
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
import { Add } from '@mui/icons-material';
|
||||||
|
|
||||||
const StyledVariantForms = styled('div')({
|
const StyledVariantForms = styled('div')({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
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<{
|
export const StrategyVariants: FC<{
|
||||||
setStrategy: React.Dispatch<
|
setStrategy: React.Dispatch<
|
||||||
React.SetStateAction<Partial<IFeatureStrategy>>
|
React.SetStateAction<Partial<IFeatureStrategy>>
|
||||||
@ -29,6 +43,8 @@ export const StrategyVariants: FC<{
|
|||||||
const { trackEvent } = usePlausibleTracker();
|
const { trackEvent } = usePlausibleTracker();
|
||||||
const [variantsEdit, setVariantsEdit] = useState<IFeatureVariantEdit[]>([]);
|
const [variantsEdit, setVariantsEdit] = useState<IFeatureVariantEdit[]>([]);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const newStrategyConfiguration = useUiFlag('newStrategyConfiguration');
|
||||||
|
|
||||||
const stickiness =
|
const stickiness =
|
||||||
strategy?.parameters && 'stickiness' in strategy?.parameters
|
strategy?.parameters && 'stickiness' in strategy?.parameters
|
||||||
? String(strategy.parameters.stickiness)
|
? 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography
|
<Typography
|
||||||
|
Loading…
Reference in New Issue
Block a user