1
0
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:
Tymoteusz Czech 2022-11-09 09:29:33 +01:00 committed by GitHub
parent 907cce9c88
commit d998f4c67a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 207 additions and 134 deletions

View File

@ -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>
</>
);

View File

@ -1,7 +1,13 @@
import { VFC } from 'react';
import { styled, Typography } from '@mui/material'; import { styled, Typography } from '@mui/material';
import { formatStrategyName } from '../../../../utils/strategyNames'; import { formatStrategyName } from 'utils/strategyNames';
import { IFeatureStrategy } from '../../../../interfaces/strategy'; import { IFeatureStrategy, IFeatureStrategyPayload } from 'interfaces/strategy';
import { CopyStrategyMsg } from './CopyStrategyMessage';
interface ICopyStrategiesMessageProps {
payload?: IFeatureStrategyPayload[];
fromEnvironment?: string;
environment?: string;
}
const MsgContainer = styled('div')(({ theme }) => ({ const MsgContainer = styled('div')(({ theme }) => ({
'&>*:nth-child(n)': { '&>*:nth-child(n)': {
@ -9,20 +15,19 @@ const MsgContainer = styled('div')(({ theme }) => ({
}, },
})); }));
export const CopyStrategiesMessage = ({ export const CopyStrategiesMessage: VFC<ICopyStrategiesMessageProps> = ({
payload, payload,
fromEnvironment, fromEnvironment,
environment, environment,
}: CopyStrategyMsg) => ( }) => (
<MsgContainer> <MsgContainer>
<Typography> <Typography>
<strong>Copy: </strong> <strong>Copy: </strong>
</Typography> </Typography>
{(payload as IFeatureStrategy[])?.map(strategy => ( {payload?.map(strategy => (
<Typography> <Typography>
<strong> <strong>
{formatStrategyName((strategy as IFeatureStrategy)?.name)}{' '} {formatStrategyName(strategy?.name || '')} strategy{' '}
strategy{' '}
</strong>{' '} </strong>{' '}
</Typography> </Typography>
))} ))}

View File

@ -1,9 +1,9 @@
import { Typography } from '@mui/material'; import { Typography } from '@mui/material';
import { formatStrategyName } from '../../../../utils/strategyNames'; import { formatStrategyName } from 'utils/strategyNames';
import { IFeatureStrategy } from '../../../../interfaces/strategy'; import { IFeatureStrategyPayload } from 'interfaces/strategy';
export interface CopyStrategyMsg { export interface CopyStrategyMsg {
payload: IFeatureStrategy | IFeatureStrategy[]; payload?: IFeatureStrategyPayload;
fromEnvironment?: string; fromEnvironment?: string;
environment?: string; environment?: string;
} }
@ -15,8 +15,7 @@ export const CopyStrategyMessage = ({
}: CopyStrategyMsg) => ( }: CopyStrategyMsg) => (
<Typography> <Typography>
<strong> <strong>
Copy {formatStrategyName((payload as IFeatureStrategy)?.name)}{' '} Copy {formatStrategyName(payload?.name || '')} strategy{' '}
strategy{' '}
</strong>{' '} </strong>{' '}
from {fromEnvironment} to {environment} from {fromEnvironment} to {environment}
</Typography> </Typography>

View File

@ -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!}
/>
}
/>
</>
);
};

View File

@ -4,18 +4,18 @@ import { SectionSeparator } from 'component/feature/FeatureView/FeatureOverview/
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi'; import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { FeatureStrategyMenu } from '../FeatureStrategyMenu/FeatureStrategyMenu';
import { PresetCard } from './PresetCard/PresetCard';
import { useStyles } from './FeatureStrategyEmpty.styles'; import { useStyles } from './FeatureStrategyEmpty.styles';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable'; import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable';
import { getFeatureStrategyIcon } from 'utils/strategyNames';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { CopyButton } from './CopyButton/CopyButton'; import { CopyButton } from './CopyButton/CopyButton';
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; import { useChangeRequestAddStrategy } from 'hooks/useChangeRequestAddStrategy';
import { useChangeRequestAddStrategy } from '../../../../hooks/useChangeRequestAddStrategy'; import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog';
import { ChangeRequestDialogue } from '../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; import { CopyStrategiesMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/CopyStrategiesMessage';
import { CopyStrategiesMessage } from '../../../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 { interface IFeatureStrategyEmptyProps {
projectId: string; projectId: string;
@ -43,9 +43,7 @@ export const FeatureStrategyEmpty = ({
environment.strategies && environment.strategies &&
environment.strategies.length > 0 environment.strategies.length > 0
); );
const isChangeRequestEnabled = useChangeRequestsEnabled(environmentId);
const { uiConfig } = useUiConfig();
const changeRequestsEnabled = uiConfig?.flags?.changeRequests;
const { const {
changeRequestDialogDetails, changeRequestDialogDetails,
@ -73,7 +71,7 @@ export const FeatureStrategyEmpty = ({
environment => environment.name === fromEnvironmentName environment => environment.name === fromEnvironmentName
)?.strategies || []; )?.strategies || [];
if (changeRequestsEnabled) { if (isChangeRequestEnabled) {
await onChangeRequestAddStrategies( await onChangeRequestAddStrategies(
environmentId, environmentId,
strategies, 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 = const canCopyFromOtherEnvironment =
otherAvailableEnvironments && otherAvailableEnvironments.length > 0; otherAvailableEnvironments && otherAvailableEnvironments.length > 0;
@ -208,25 +176,41 @@ export const FeatureStrategyEmpty = ({
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' }, gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },
}} }}
> >
<PresetCard <AddFromTemplateCard
title="Standard strategy" title="Standard strategy"
Icon={getFeatureStrategyIcon('default')}
onClick={onAddSimpleStrategy}
projectId={projectId} projectId={projectId}
featureId={featureId}
environmentId={environmentId} environmentId={environmentId}
onAfterAddStrategy={onAfterAddStrategy}
Icon={getFeatureStrategyIcon('default')}
strategy={{
name: 'default',
parameters: {},
constraints: [],
}}
> >
The standard strategy is strictly on/off for your entire The standard strategy is strictly on/off for your entire
userbase. userbase.
</PresetCard> </AddFromTemplateCard>
<PresetCard <AddFromTemplateCard
title="Gradual rollout" title="Gradual rollout"
Icon={getFeatureStrategyIcon('flexibleRollout')}
onClick={onAddGradualRolloutStrategy}
projectId={projectId} projectId={projectId}
featureId={featureId}
environmentId={environmentId} 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. Roll out to a percentage of your userbase.
</PresetCard> </AddFromTemplateCard>
</Box> </Box>
</div> </div>
</> </>

View File

@ -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>
);

View File

@ -1,10 +1,7 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import { import { IFeatureStrategyPayload } from '../interfaces/strategy';
IFeatureStrategy,
IFeatureStrategyPayload,
} from '../interfaces/strategy';
import { useChangeRequestApi } from './api/actions/useChangeRequestApi/useChangeRequestApi'; import { useChangeRequestApi } from './api/actions/useChangeRequestApi/useChangeRequestApi';
import { useChangeRequestOpen } from './api/getters/useChangeRequestOpen/useChangeRequestOpen'; import { useChangeRequestOpen } from './api/getters/useChangeRequestOpen/useChangeRequestOpen';
@ -24,8 +21,8 @@ export const useChangeRequestAddStrategy = (
const [changeRequestDialogDetails, setChangeRequestDialogDetails] = const [changeRequestDialogDetails, setChangeRequestDialogDetails] =
useState<{ useState<{
strategy?: IFeatureStrategy; strategy?: IFeatureStrategyPayload;
strategies?: IFeatureStrategy[]; strategies?: IFeatureStrategyPayload[];
featureName?: string; featureName?: string;
environment?: string; environment?: string;
fromEnvironment?: string; fromEnvironment?: string;
@ -35,7 +32,7 @@ export const useChangeRequestAddStrategy = (
const onChangeRequestAddStrategy = useCallback( const onChangeRequestAddStrategy = useCallback(
( (
environment: string, environment: string,
strategy: IFeatureStrategy, strategy: IFeatureStrategyPayload,
fromEnvironment?: string fromEnvironment?: string
) => { ) => {
setChangeRequestDialogDetails({ setChangeRequestDialogDetails({
@ -52,7 +49,7 @@ export const useChangeRequestAddStrategy = (
const onChangeRequestAddStrategies = useCallback( const onChangeRequestAddStrategies = useCallback(
( (
environment: string, environment: string,
strategies: IFeatureStrategy[], strategies: IFeatureStrategyPayload[],
fromEnvironment: string fromEnvironment: string
) => { ) => {
setChangeRequestDialogDetails({ setChangeRequestDialogDetails({

View File

@ -16,6 +16,7 @@ export interface IFeatureStrategyParameters {
} }
export interface IFeatureStrategyPayload { export interface IFeatureStrategyPayload {
id?: string;
name?: string; name?: string;
constraints: IConstraint[]; constraints: IConstraint[];
parameters: IFeatureStrategyParameters; parameters: IFeatureStrategyParameters;