mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-04 00:18:40 +01:00
feat: suggest strategy from template (#2340)
This commit is contained in:
parent
907cce9c88
commit
d998f4c67a
@ -0,0 +1,21 @@
|
||||
import { VFC } from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import { formatStrategyName } from 'utils/strategyNames';
|
||||
import { IFeatureStrategyPayload } from 'interfaces/strategy';
|
||||
|
||||
interface IAddStrategyMessageProps {
|
||||
payload?: IFeatureStrategyPayload;
|
||||
environment?: string;
|
||||
}
|
||||
|
||||
export const AddStrategyMessage: VFC<IAddStrategyMessageProps> = ({
|
||||
payload,
|
||||
environment,
|
||||
}) => (
|
||||
<>
|
||||
<Typography component="span">Add </Typography>
|
||||
<strong>
|
||||
{formatStrategyName(payload?.name || '')} strategy
|
||||
</strong> to <strong>{environment}</strong>
|
||||
</>
|
||||
);
|
@ -1,7 +1,13 @@
|
||||
import { VFC } from 'react';
|
||||
import { styled, Typography } from '@mui/material';
|
||||
import { formatStrategyName } from '../../../../utils/strategyNames';
|
||||
import { IFeatureStrategy } from '../../../../interfaces/strategy';
|
||||
import { CopyStrategyMsg } from './CopyStrategyMessage';
|
||||
import { formatStrategyName } from 'utils/strategyNames';
|
||||
import { IFeatureStrategy, IFeatureStrategyPayload } from 'interfaces/strategy';
|
||||
|
||||
interface ICopyStrategiesMessageProps {
|
||||
payload?: IFeatureStrategyPayload[];
|
||||
fromEnvironment?: string;
|
||||
environment?: string;
|
||||
}
|
||||
|
||||
const MsgContainer = styled('div')(({ theme }) => ({
|
||||
'&>*:nth-child(n)': {
|
||||
@ -9,20 +15,19 @@ const MsgContainer = styled('div')(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
export const CopyStrategiesMessage = ({
|
||||
export const CopyStrategiesMessage: VFC<ICopyStrategiesMessageProps> = ({
|
||||
payload,
|
||||
fromEnvironment,
|
||||
environment,
|
||||
}: CopyStrategyMsg) => (
|
||||
}) => (
|
||||
<MsgContainer>
|
||||
<Typography>
|
||||
<strong>Copy: </strong>
|
||||
</Typography>
|
||||
{(payload as IFeatureStrategy[])?.map(strategy => (
|
||||
{payload?.map(strategy => (
|
||||
<Typography>
|
||||
<strong>
|
||||
{formatStrategyName((strategy as IFeatureStrategy)?.name)}{' '}
|
||||
strategy{' '}
|
||||
{formatStrategyName(strategy?.name || '')} strategy{' '}
|
||||
</strong>{' '}
|
||||
</Typography>
|
||||
))}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Typography } from '@mui/material';
|
||||
import { formatStrategyName } from '../../../../utils/strategyNames';
|
||||
import { IFeatureStrategy } from '../../../../interfaces/strategy';
|
||||
import { formatStrategyName } from 'utils/strategyNames';
|
||||
import { IFeatureStrategyPayload } from 'interfaces/strategy';
|
||||
|
||||
export interface CopyStrategyMsg {
|
||||
payload: IFeatureStrategy | IFeatureStrategy[];
|
||||
payload?: IFeatureStrategyPayload;
|
||||
fromEnvironment?: string;
|
||||
environment?: string;
|
||||
}
|
||||
@ -15,8 +15,7 @@ export const CopyStrategyMessage = ({
|
||||
}: CopyStrategyMsg) => (
|
||||
<Typography>
|
||||
<strong>
|
||||
Copy {formatStrategyName((payload as IFeatureStrategy)?.name)}{' '}
|
||||
strategy{' '}
|
||||
Copy {formatStrategyName(payload?.name || '')} strategy{' '}
|
||||
</strong>{' '}
|
||||
from {fromEnvironment} to {environment}
|
||||
</Typography>
|
||||
|
@ -0,0 +1,130 @@
|
||||
import { ElementType, FC } from 'react';
|
||||
import { Card, CardContent, Typography, styled, Box } from '@mui/material';
|
||||
import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
|
||||
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { AddStrategyMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/AddStrategyMessage';
|
||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
import { useChangeRequestAddStrategy } from 'hooks/useChangeRequestAddStrategy';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||
import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
||||
import { IFeatureStrategyPayload } from 'interfaces/strategy';
|
||||
|
||||
interface IAddFromTemplateCardProps {
|
||||
title: string;
|
||||
featureId: string;
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
strategy: IFeatureStrategyPayload;
|
||||
Icon: ElementType;
|
||||
onAfterAddStrategy: () => void;
|
||||
}
|
||||
|
||||
const StyledCard = styled(Card)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
}));
|
||||
|
||||
export const AddFromTemplateCard: FC<IAddFromTemplateCardProps> = ({
|
||||
title,
|
||||
children,
|
||||
featureId,
|
||||
projectId,
|
||||
environmentId,
|
||||
strategy,
|
||||
Icon,
|
||||
onAfterAddStrategy,
|
||||
}) => {
|
||||
const { addStrategyToFeature } = useFeatureStrategyApi();
|
||||
const { setToastApiError } = useToast();
|
||||
|
||||
const isChangeRequestEnabled = useChangeRequestsEnabled(environmentId);
|
||||
|
||||
const {
|
||||
changeRequestDialogDetails,
|
||||
onChangeRequestAddStrategy,
|
||||
onChangeRequestAddStrategyConfirm,
|
||||
onChangeRequestAddStrategyClose,
|
||||
} = useChangeRequestAddStrategy(projectId, featureId, 'addStrategy');
|
||||
|
||||
const onStrategyAdd = async () => {
|
||||
try {
|
||||
if (isChangeRequestEnabled) {
|
||||
onChangeRequestAddStrategy(environmentId, strategy);
|
||||
} else {
|
||||
await addStrategyToFeature(
|
||||
projectId,
|
||||
featureId,
|
||||
environmentId,
|
||||
strategy
|
||||
);
|
||||
onAfterAddStrategy();
|
||||
}
|
||||
} catch (error) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledCard variant="outlined">
|
||||
<CardContent
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight="medium"
|
||||
sx={{ mb: 0.5, display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
<Icon color="disabled" sx={{ mr: 1 }} /> {title}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
component="p"
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
ml: 'auto',
|
||||
mt: 'auto',
|
||||
pt: 1,
|
||||
mr: { xs: 'auto', sm: 0 },
|
||||
}}
|
||||
>
|
||||
<PermissionButton
|
||||
permission={CREATE_FEATURE_STRATEGY}
|
||||
projectId={projectId}
|
||||
environmentId={environmentId}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={onStrategyAdd}
|
||||
>
|
||||
Use template
|
||||
</PermissionButton>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</StyledCard>
|
||||
<ChangeRequestDialogue
|
||||
isOpen={changeRequestDialogDetails.isOpen}
|
||||
onClose={onChangeRequestAddStrategyClose}
|
||||
environment={changeRequestDialogDetails?.environment}
|
||||
onConfirm={onChangeRequestAddStrategyConfirm}
|
||||
messageComponent={
|
||||
<AddStrategyMessage
|
||||
environment={environmentId}
|
||||
payload={changeRequestDialogDetails.strategy!}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -4,18 +4,18 @@ import { SectionSeparator } from 'component/feature/FeatureView/FeatureOverview/
|
||||
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import { FeatureStrategyMenu } from '../FeatureStrategyMenu/FeatureStrategyMenu';
|
||||
import { PresetCard } from './PresetCard/PresetCard';
|
||||
import { useStyles } from './FeatureStrategyEmpty.styles';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable';
|
||||
import { getFeatureStrategyIcon } from 'utils/strategyNames';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { CopyButton } from './CopyButton/CopyButton';
|
||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { useChangeRequestAddStrategy } from '../../../../hooks/useChangeRequestAddStrategy';
|
||||
import { ChangeRequestDialogue } from '../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
|
||||
import { CopyStrategiesMessage } from '../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/CopyStrategiesMessage';
|
||||
import { useChangeRequestAddStrategy } from 'hooks/useChangeRequestAddStrategy';
|
||||
import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
|
||||
import { CopyStrategiesMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/CopyStrategiesMessage';
|
||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||
import { getFeatureStrategyIcon } from 'utils/strategyNames';
|
||||
import { AddFromTemplateCard } from './AddFromTemplateCard/AddFromTemplateCard';
|
||||
import { FeatureStrategyMenu } from '../FeatureStrategyMenu/FeatureStrategyMenu';
|
||||
|
||||
interface IFeatureStrategyEmptyProps {
|
||||
projectId: string;
|
||||
@ -43,9 +43,7 @@ export const FeatureStrategyEmpty = ({
|
||||
environment.strategies &&
|
||||
environment.strategies.length > 0
|
||||
);
|
||||
|
||||
const { uiConfig } = useUiConfig();
|
||||
const changeRequestsEnabled = uiConfig?.flags?.changeRequests;
|
||||
const isChangeRequestEnabled = useChangeRequestsEnabled(environmentId);
|
||||
|
||||
const {
|
||||
changeRequestDialogDetails,
|
||||
@ -73,7 +71,7 @@ export const FeatureStrategyEmpty = ({
|
||||
environment => environment.name === fromEnvironmentName
|
||||
)?.strategies || [];
|
||||
|
||||
if (changeRequestsEnabled) {
|
||||
if (isChangeRequestEnabled) {
|
||||
await onChangeRequestAddStrategies(
|
||||
environmentId,
|
||||
strategies,
|
||||
@ -105,36 +103,6 @@ export const FeatureStrategyEmpty = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onAddSimpleStrategy = async () => {
|
||||
try {
|
||||
await addStrategyToFeature(projectId, featureId, environmentId, {
|
||||
name: 'default',
|
||||
parameters: {},
|
||||
constraints: [],
|
||||
});
|
||||
onAfterAddStrategy();
|
||||
} catch (error) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
const onAddGradualRolloutStrategy = async () => {
|
||||
try {
|
||||
await addStrategyToFeature(projectId, featureId, environmentId, {
|
||||
name: 'flexibleRollout',
|
||||
parameters: {
|
||||
rollout: '50',
|
||||
stickiness: 'default',
|
||||
groupId: feature.name,
|
||||
},
|
||||
constraints: [],
|
||||
});
|
||||
onAfterAddStrategy();
|
||||
} catch (error) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
const canCopyFromOtherEnvironment =
|
||||
otherAvailableEnvironments && otherAvailableEnvironments.length > 0;
|
||||
|
||||
@ -208,25 +176,41 @@ export const FeatureStrategyEmpty = ({
|
||||
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },
|
||||
}}
|
||||
>
|
||||
<PresetCard
|
||||
<AddFromTemplateCard
|
||||
title="Standard strategy"
|
||||
Icon={getFeatureStrategyIcon('default')}
|
||||
onClick={onAddSimpleStrategy}
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={environmentId}
|
||||
onAfterAddStrategy={onAfterAddStrategy}
|
||||
Icon={getFeatureStrategyIcon('default')}
|
||||
strategy={{
|
||||
name: 'default',
|
||||
parameters: {},
|
||||
constraints: [],
|
||||
}}
|
||||
>
|
||||
The standard strategy is strictly on/off for your entire
|
||||
userbase.
|
||||
</PresetCard>
|
||||
<PresetCard
|
||||
</AddFromTemplateCard>
|
||||
<AddFromTemplateCard
|
||||
title="Gradual rollout"
|
||||
Icon={getFeatureStrategyIcon('flexibleRollout')}
|
||||
onClick={onAddGradualRolloutStrategy}
|
||||
projectId={projectId}
|
||||
featureId={featureId}
|
||||
environmentId={environmentId}
|
||||
onAfterAddStrategy={onAfterAddStrategy}
|
||||
Icon={getFeatureStrategyIcon('flexibleRollout')}
|
||||
strategy={{
|
||||
name: 'flexibleRollout',
|
||||
parameters: {
|
||||
rollout: '50',
|
||||
stickiness: 'default',
|
||||
groupId: feature.name,
|
||||
},
|
||||
constraints: [],
|
||||
}}
|
||||
>
|
||||
Roll out to a percentage of your userbase.
|
||||
</PresetCard>
|
||||
</AddFromTemplateCard>
|
||||
</Box>
|
||||
</div>
|
||||
</>
|
||||
|
@ -1,64 +0,0 @@
|
||||
import { ElementType, FC } from 'react';
|
||||
import { Card, CardContent, Typography, styled, Box } from '@mui/material';
|
||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||
import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
||||
|
||||
interface IPresetCardProps {
|
||||
title: string;
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
onClick: () => void;
|
||||
Icon: ElementType;
|
||||
}
|
||||
|
||||
const StyledCard = styled(Card)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
}));
|
||||
|
||||
export const PresetCard: FC<IPresetCardProps> = ({
|
||||
title,
|
||||
children,
|
||||
Icon,
|
||||
projectId,
|
||||
environmentId,
|
||||
onClick,
|
||||
}) => (
|
||||
<StyledCard variant="outlined">
|
||||
<CardContent
|
||||
sx={{ display: 'flex', flexDirection: 'column', flexGrow: 1 }}
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight="medium"
|
||||
sx={{ mb: 0.5, display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
<Icon color="disabled" sx={{ mr: 1 }} /> {title}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" component="p">
|
||||
{children}
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
ml: 'auto',
|
||||
mt: 'auto',
|
||||
pt: 1,
|
||||
mr: { xs: 'auto', sm: 0 },
|
||||
}}
|
||||
>
|
||||
<PermissionButton
|
||||
permission={CREATE_FEATURE_STRATEGY}
|
||||
projectId={projectId}
|
||||
environmentId={environmentId}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={onClick}
|
||||
>
|
||||
Use template
|
||||
</PermissionButton>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</StyledCard>
|
||||
);
|
@ -1,10 +1,7 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import {
|
||||
IFeatureStrategy,
|
||||
IFeatureStrategyPayload,
|
||||
} from '../interfaces/strategy';
|
||||
import { IFeatureStrategyPayload } from '../interfaces/strategy';
|
||||
import { useChangeRequestApi } from './api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||
import { useChangeRequestOpen } from './api/getters/useChangeRequestOpen/useChangeRequestOpen';
|
||||
|
||||
@ -24,8 +21,8 @@ export const useChangeRequestAddStrategy = (
|
||||
|
||||
const [changeRequestDialogDetails, setChangeRequestDialogDetails] =
|
||||
useState<{
|
||||
strategy?: IFeatureStrategy;
|
||||
strategies?: IFeatureStrategy[];
|
||||
strategy?: IFeatureStrategyPayload;
|
||||
strategies?: IFeatureStrategyPayload[];
|
||||
featureName?: string;
|
||||
environment?: string;
|
||||
fromEnvironment?: string;
|
||||
@ -35,7 +32,7 @@ export const useChangeRequestAddStrategy = (
|
||||
const onChangeRequestAddStrategy = useCallback(
|
||||
(
|
||||
environment: string,
|
||||
strategy: IFeatureStrategy,
|
||||
strategy: IFeatureStrategyPayload,
|
||||
fromEnvironment?: string
|
||||
) => {
|
||||
setChangeRequestDialogDetails({
|
||||
@ -52,7 +49,7 @@ export const useChangeRequestAddStrategy = (
|
||||
const onChangeRequestAddStrategies = useCallback(
|
||||
(
|
||||
environment: string,
|
||||
strategies: IFeatureStrategy[],
|
||||
strategies: IFeatureStrategyPayload[],
|
||||
fromEnvironment: string
|
||||
) => {
|
||||
setChangeRequestDialogDetails({
|
||||
|
@ -16,6 +16,7 @@ export interface IFeatureStrategyParameters {
|
||||
}
|
||||
|
||||
export interface IFeatureStrategyPayload {
|
||||
id?: string;
|
||||
name?: string;
|
||||
constraints: IConstraint[];
|
||||
parameters: IFeatureStrategyParameters;
|
||||
|
Loading…
Reference in New Issue
Block a user