1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-31 13:47:02 +02:00

fix: add strategy release templates visibility for non Enterprise (#10401)

https://linear.app/unleash/issue/2-3711/add-strategy-modal-shows-release-templates-section-for-non-enterprise

Fixes a bug for non-Enterprise where release templates were mentioned
(and even linked to) even though they were not available for these
plans. When following the link the result was a page that did not
render.

Also slightly refactors and improves this component.

<img width="870" height="496" alt="image"
src="https://github.com/user-attachments/assets/47499e21-73fc-4ddf-8eed-6146be31b074"
/>
This commit is contained in:
Nuno Góis 2025-07-23 20:17:22 +01:00 committed by GitHub
parent 0e015d6686
commit 45e5b217aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -8,7 +8,6 @@ import {
} from '@mui/material'; } from '@mui/material';
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies'; import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
import { FeatureStrategyMenuCard } from '../FeatureStrategyMenuCard/FeatureStrategyMenuCard.tsx'; import { FeatureStrategyMenuCard } from '../FeatureStrategyMenuCard/FeatureStrategyMenuCard.tsx';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates'; import { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates';
import { FeatureReleasePlanCard } from '../FeatureReleasePlanCard/FeatureReleasePlanCard.tsx'; import { FeatureReleasePlanCard } from '../FeatureReleasePlanCard/FeatureReleasePlanCard.tsx';
import type { IReleasePlanTemplate } from 'interfaces/releasePlans'; import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
@ -16,6 +15,8 @@ import { useNavigate } from 'react-router-dom';
import CloseIcon from '@mui/icons-material/Close'; import CloseIcon from '@mui/icons-material/Close';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import FactCheckOutlinedIcon from '@mui/icons-material/FactCheckOutlined'; import FactCheckOutlinedIcon from '@mui/icons-material/FactCheckOutlined';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig.ts';
import { useUiFlag } from 'hooks/useUiFlag.ts';
interface IFeatureStrategyMenuCardsProps { interface IFeatureStrategyMenuCardsProps {
projectId: string; projectId: string;
@ -27,17 +28,6 @@ interface IFeatureStrategyMenuCardsProps {
onClose: () => void; onClose: () => void;
} }
const StyledTypography = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
padding: theme.spacing(1, 4),
width: '100%',
}));
const StyledLink = styled(Link)(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
cursor: 'pointer',
})) as typeof Link;
const GridContainer = styled(Box)(() => ({ const GridContainer = styled(Box)(() => ({
width: '100%', width: '100%',
display: 'flex', display: 'flex',
@ -48,15 +38,17 @@ const ScrollableContent = styled(Box)(({ theme }) => ({
width: '100%', width: '100%',
maxHeight: '70vh', maxHeight: '70vh',
overflowY: 'auto', overflowY: 'auto',
padding: theme.spacing(1, 0, 1, 0), padding: theme.spacing(4),
paddingTop: 0,
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(3),
})); }));
const GridSection = styled(Box)(({ theme }) => ({ const GridSection = styled(Box)(({ theme }) => ({
display: 'grid', display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)', gridTemplateColumns: 'repeat(2, 1fr)',
gap: theme.spacing(1.5), gap: theme.spacing(1.5),
padding: theme.spacing(0, 4),
marginBottom: theme.spacing(3),
width: '100%', width: '100%',
})); }));
@ -82,7 +74,7 @@ const SectionTitle = styled(Box)(({ theme }) => ({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: theme.spacing(0.5), gap: theme.spacing(0.5),
padding: theme.spacing(0, 4, 1, 4), marginBottom: theme.spacing(1),
width: '100%', width: '100%',
})); }));
@ -110,7 +102,6 @@ const EmptyStateContainer = styled(Box)(({ theme }) => ({
backgroundColor: theme.palette.neutral.light, backgroundColor: theme.palette.neutral.light,
borderRadius: theme.shape.borderRadiusMedium, borderRadius: theme.shape.borderRadiusMedium,
padding: theme.spacing(3), padding: theme.spacing(3),
margin: theme.spacing(0, 4),
width: 'auto', width: 'auto',
})); }));
@ -144,10 +135,12 @@ export const FeatureStrategyMenuCards = ({
onReviewReleasePlan, onReviewReleasePlan,
onClose, onClose,
}: IFeatureStrategyMenuCardsProps) => { }: IFeatureStrategyMenuCardsProps) => {
const { isEnterprise } = useUiConfig();
const releasePlansEnabled = useUiFlag('releasePlans');
const { strategies } = useStrategies(); const { strategies } = useStrategies();
const { templates } = useReleasePlanTemplates(); const { templates } = useReleasePlanTemplates();
const navigate = useNavigate(); const navigate = useNavigate();
const allStrategies = !onlyReleasePlans;
const preDefinedStrategies = strategies.filter( const preDefinedStrategies = strategies.filter(
(strategy) => !strategy.deprecated && !strategy.editable, (strategy) => !strategy.deprecated && !strategy.editable,
@ -163,6 +156,65 @@ export const FeatureStrategyMenuCards = ({
description: description:
'This is the default strategy defined for this environment in the project', 'This is the default strategy defined for this environment in the project',
}; };
const renderReleasePlanTemplates = () => {
if (!isEnterprise() || !releasePlansEnabled) {
return null;
}
if (!templates.length) {
return (
<EmptyStateContainer>
<EmptyStateTitle>
<StyledIcon>
<FactCheckOutlinedIcon />
</StyledIcon>
Create your own release templates
</EmptyStateTitle>
<EmptyStateDescription>
Standardize your rollouts and save time by reusing
predefined strategies. Find release templates in the
side menu under{' '}
<ClickableBoldText
onClick={() => navigate('/release-templates')}
>
Configure &gt; Release templates
</ClickableBoldText>
</EmptyStateDescription>
</EmptyStateContainer>
);
}
return (
<Box>
<SectionTitle>
<Typography color='inherit' variant='body2'>
Apply a release template
</Typography>
<Tooltip
title='Use a predefined template to roll out features to users'
arrow
>
<StyledInfoIcon />
</Tooltip>
</SectionTitle>
<GridSection>
{templates.map((template) => (
<CardWrapper key={template.id}>
<FeatureReleasePlanCard
template={template}
onClick={() => onAddReleasePlan(template)}
onPreviewClick={() =>
onReviewReleasePlan(template)
}
/>
</CardWrapper>
))}
</GridSection>
</Box>
);
};
return ( return (
<GridContainer> <GridContainer>
<TitleRow> <TitleRow>
@ -179,167 +231,77 @@ export const FeatureStrategyMenuCards = ({
</IconButton> </IconButton>
</TitleRow> </TitleRow>
<ScrollableContent> <ScrollableContent>
{allStrategies ? ( {onlyReleasePlans ? (
renderReleasePlanTemplates()
) : (
<> <>
<SectionTitle> <Box>
<Typography color='inherit' variant='body2'>
Pre-defined strategy types
</Typography>
<Tooltip
title='Select a starting setup, and customize the strategy to your need with targeting and variants'
arrow
>
<StyledInfoIcon />
</Tooltip>
</SectionTitle>
<GridSection>
<CardWrapper key={defaultStrategy.name}>
<FeatureStrategyMenuCard
projectId={projectId}
featureId={featureId}
environmentId={environmentId}
strategy={defaultStrategy}
defaultStrategy={true}
onClose={onClose}
/>
</CardWrapper>
{preDefinedStrategies.map((strategy) => (
<CardWrapper key={strategy.name}>
<FeatureStrategyMenuCard
projectId={projectId}
featureId={featureId}
environmentId={environmentId}
strategy={strategy}
onClose={onClose}
/>
</CardWrapper>
))}
</GridSection>
</>
) : null}
<ConditionallyRender
condition={templates.length > 0}
show={
<>
<SectionTitle> <SectionTitle>
<Typography color='inherit' variant='body2'> <Typography color='inherit' variant='body2'>
Apply a release template Pre-defined strategy types
</Typography> </Typography>
<Tooltip <Tooltip
title='Use one of the pre-defined templates defined in your company for rolling out features to users' title='Select a starting setup, and customize the strategy to your need with targeting and variants'
arrow arrow
> >
<StyledInfoIcon /> <StyledInfoIcon />
</Tooltip> </Tooltip>
</SectionTitle> </SectionTitle>
<GridSection> <GridSection>
{templates.map((template) => ( <CardWrapper key={defaultStrategy.name}>
<CardWrapper key={template.id}> <FeatureStrategyMenuCard
<FeatureReleasePlanCard projectId={projectId}
template={template} featureId={featureId}
onClick={() => environmentId={environmentId}
onAddReleasePlan(template) strategy={defaultStrategy}
} defaultStrategy
onPreviewClick={() => onClose={onClose}
onReviewReleasePlan(template) />
} </CardWrapper>
{preDefinedStrategies.map((strategy) => (
<CardWrapper key={strategy.name}>
<FeatureStrategyMenuCard
projectId={projectId}
featureId={featureId}
environmentId={environmentId}
strategy={strategy}
onClose={onClose}
/> />
</CardWrapper> </CardWrapper>
))} ))}
</GridSection> </GridSection>
</> </Box>
} {renderReleasePlanTemplates()}
elseShow={ {customStrategies.length > 0 && (
<EmptyStateContainer> <Box>
<EmptyStateTitle> <SectionTitle>
<StyledIcon> <Typography color='inherit' variant='body2'>
<FactCheckOutlinedIcon /> Custom strategies
</StyledIcon> </Typography>
Create your own templates <Tooltip
</EmptyStateTitle> title='Custom strategies you have defined in Unleash'
<EmptyStateDescription> arrow
Standardize how you do rollouts and make it more
efficient without having to set up the same
stategies from time to time. You find it in the
sidemenu under{' '}
<ClickableBoldText
onClick={() =>
navigate('/release-templates')
}
>
Configure &gt; Release templates
</ClickableBoldText>
</EmptyStateDescription>
</EmptyStateContainer>
}
/>
<ConditionallyRender
condition={templates.length === 0 && onlyReleasePlans}
show={
<>
<StyledTypography
color='textSecondary'
sx={{
padding: (theme) =>
theme.spacing(1, 2, 0, 2),
}}
>
<p>No templates created.</p>
<p>
Go to&nbsp;
<StyledLink
onClick={() =>
navigate('/release-templates')
}
> >
Release templates <StyledInfoIcon />
</StyledLink> </Tooltip>
&nbsp;to get started </SectionTitle>
</p> <GridSection>
</StyledTypography> {customStrategies.map((strategy) => (
</> <CardWrapper key={strategy.name}>
} <FeatureStrategyMenuCard
/> projectId={projectId}
{allStrategies ? ( featureId={featureId}
<> environmentId={environmentId}
<ConditionallyRender strategy={strategy}
condition={customStrategies.length > 0} onClose={onClose}
show={ />
<> </CardWrapper>
<SectionTitle> ))}
<Typography </GridSection>
color='inherit' </Box>
variant='body2' )}
>
Custom strategies
</Typography>
<Tooltip
title='Custom strategies you have defined in Unleash'
arrow
>
<StyledInfoIcon />
</Tooltip>
</SectionTitle>
<GridSection>
{customStrategies.map((strategy) => (
<CardWrapper key={strategy.name}>
<FeatureStrategyMenuCard
projectId={projectId}
featureId={featureId}
environmentId={
environmentId
}
strategy={strategy}
onClose={onClose}
/>
</CardWrapper>
))}
</GridSection>
</>
}
/>
</> </>
) : null} )}
</ScrollableContent> </ScrollableContent>
</GridContainer> </GridContainer>
); );