1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-04 13:48:56 +02:00

chore: add feature configuration based on addConfiguration flag (#10420)

https://linear.app/unleash/issue/2-3729/single-add-configuration-button-based-on-flag

Shows a single "Add configuration" button based on whether the new
`addConfiguration` flag is enabled.

This button then shows our "Add configuration" modal which allows you to
choose how to proceed in terms of your feature flag configuration. Also
updates this modal to better match the latest sketches.

Includes scouting.

### Single "Add configuration" button
<img width="738" height="121" alt="image"
src="https://github.com/user-attachments/assets/9cce7fba-5e0c-42e0-a3d1-8ccc34f730bb"
/>

### Modal
<img width="983" height="663" alt="image"
src="https://github.com/user-attachments/assets/b59abad2-f1cd-4b62-bf2e-9c3b24cbb60e"
/>
This commit is contained in:
Nuno Góis 2025-07-29 08:27:13 +01:00 committed by GitHub
parent 0a9d6437c5
commit 15449e83d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 149 additions and 122 deletions

View File

@ -27,7 +27,10 @@ const CardContent = styled('div')(({ theme }) => ({
const HoverButtonsContainer = styled('div')(({ theme }) => ({
position: 'absolute',
right: theme.spacing(2),
background: theme.palette.background.paper,
padding: theme.spacing(1),
top: theme.spacing(1),
right: theme.spacing(1),
display: 'flex',
gap: theme.spacing(1),
opacity: 0,

View File

@ -75,9 +75,10 @@ export const FeatureStrategyMenu = ({
usePendingChangeRequests(projectId);
const { refetch } = useReleasePlans(projectId, featureId, environmentId);
const { addReleasePlanToFeature } = useReleasePlansApi();
const { isOss } = useUiConfig();
const { isEnterprise } = useUiConfig();
const addConfigurationEnabled = useUiFlag('addConfiguration');
const releasePlansEnabled = useUiFlag('releasePlans');
const displayReleasePlanButton = !isOss() && releasePlansEnabled;
const displayReleasePlanButton = isEnterprise() && releasePlansEnabled;
const crProtected =
releasePlansEnabled && isChangeRequestConfigured(environmentId);
@ -161,56 +162,81 @@ export const FeatureStrategyMenu = ({
return (
<StyledStrategyMenu onClick={(event) => event.stopPropagation()}>
{displayReleasePlanButton ? (
{addConfigurationEnabled ? (
<PermissionButton
data-testid='ADD_TEMPLATE_BUTTON'
data-testid='ADD_STRATEGY_BUTTON'
permission={CREATE_FEATURE_STRATEGY}
projectId={projectId}
environmentId={environmentId}
onClick={openReleasePlans}
onClick={openMoreStrategies}
aria-labelledby={dialogId}
variant='outlined'
variant={variant}
sx={{ minWidth: matchWidth ? '282px' : 'auto' }}
disabled={Boolean(disableReason)}
tooltipProps={{
title: disableReason ? disableReason : undefined,
}}
>
Use template
Add configuration
</PermissionButton>
) : null}
) : (
<>
{displayReleasePlanButton ? (
<PermissionButton
data-testid='ADD_TEMPLATE_BUTTON'
permission={CREATE_FEATURE_STRATEGY}
projectId={projectId}
environmentId={environmentId}
onClick={openReleasePlans}
aria-labelledby={dialogId}
variant='outlined'
sx={{ minWidth: matchWidth ? '282px' : 'auto' }}
disabled={Boolean(disableReason)}
tooltipProps={{
title: disableReason
? disableReason
: undefined,
}}
>
Use template
</PermissionButton>
) : null}
<PermissionButton
data-testid='ADD_STRATEGY_BUTTON'
permission={CREATE_FEATURE_STRATEGY}
projectId={projectId}
environmentId={environmentId}
onClick={openDefaultStrategyCreationModal}
aria-labelledby={dialogId}
variant={variant}
sx={{ minWidth: matchWidth ? '282px' : 'auto' }}
disabled={Boolean(disableReason)}
tooltipProps={{
title: disableReason ? disableReason : undefined,
}}
>
{label}
</PermissionButton>
<PermissionButton
data-testid='ADD_STRATEGY_BUTTON'
permission={CREATE_FEATURE_STRATEGY}
projectId={projectId}
environmentId={environmentId}
onClick={openDefaultStrategyCreationModal}
aria-labelledby={dialogId}
variant={variant}
sx={{ minWidth: matchWidth ? '282px' : 'auto' }}
disabled={Boolean(disableReason)}
tooltipProps={{
title: disableReason ? disableReason : undefined,
}}
>
{label}
</PermissionButton>
<StyledAdditionalMenuButton
permission={CREATE_FEATURE_STRATEGY}
projectId={projectId}
environmentId={environmentId}
onClick={openMoreStrategies}
variant='outlined'
hideLockIcon
disabled={Boolean(disableReason)}
tooltipProps={{
title: disableReason ? disableReason : 'More strategies',
}}
>
<MoreVert />
</StyledAdditionalMenuButton>
<StyledAdditionalMenuButton
permission={CREATE_FEATURE_STRATEGY}
projectId={projectId}
environmentId={environmentId}
onClick={openMoreStrategies}
variant='outlined'
hideLockIcon
disabled={Boolean(disableReason)}
tooltipProps={{
title: disableReason
? disableReason
: 'More strategies',
}}
>
<MoreVert />
</StyledAdditionalMenuButton>
</>
)}
<Dialog
open={isStrategyMenuDialogOpen}
onClose={onClose}

View File

@ -1,11 +1,4 @@
import {
Link,
styled,
Typography,
Box,
IconButton,
Tooltip,
} from '@mui/material';
import { Link, styled, Typography, Box, IconButton } from '@mui/material';
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
import { FeatureStrategyMenuCard } from '../FeatureStrategyMenuCard/FeatureStrategyMenuCard.tsx';
import { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates';
@ -13,10 +6,10 @@ import { FeatureReleasePlanCard } from '../FeatureReleasePlanCard/FeatureRelease
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
import { useNavigate } from 'react-router-dom';
import CloseIcon from '@mui/icons-material/Close';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import FactCheckOutlinedIcon from '@mui/icons-material/FactCheckOutlined';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig.ts';
import { useUiFlag } from 'hooks/useUiFlag.ts';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon.tsx';
interface IFeatureStrategyMenuCardsProps {
projectId: string;
@ -78,11 +71,6 @@ const SectionTitle = styled(Box)(({ theme }) => ({
width: '100%',
}));
const StyledInfoIcon = styled(InfoOutlinedIcon)(({ theme }) => ({
fontSize: theme.typography.body2.fontSize,
color: theme.palette.text.secondary,
}));
const StyledIcon = styled('span')(({ theme }) => ({
width: theme.spacing(3),
'& > svg': {
@ -162,55 +150,51 @@ export const FeatureStrategyMenuCards = ({
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
Release templates
</Typography>
<Tooltip
title='Use a predefined template to roll out features to users'
arrow
>
<StyledInfoIcon />
</Tooltip>
<HelpIcon
tooltip='Use a predefined template to roll out features to users'
size='16px'
/>
</SectionTitle>
<GridSection>
{templates.map((template) => (
<CardWrapper key={template.id}>
<FeatureReleasePlanCard
template={template}
onClick={() => onAddReleasePlan(template)}
onPreviewClick={() =>
onReviewReleasePlan(template)
}
/>
</CardWrapper>
))}
</GridSection>
{!templates.length ? (
<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>
) : (
<GridSection>
{templates.map((template) => (
<CardWrapper key={template.id}>
<FeatureReleasePlanCard
template={template}
onClick={() => onAddReleasePlan(template)}
onPreviewClick={() =>
onReviewReleasePlan(template)
}
/>
</CardWrapper>
))}
</GridSection>
)}
</Box>
);
};
@ -219,7 +203,7 @@ export const FeatureStrategyMenuCards = ({
<GridContainer>
<TitleRow>
<TitleText variant='h2'>
{onlyReleasePlans ? 'Select template' : 'Select strategy'}
{onlyReleasePlans ? 'Select template' : 'Add configuration'}
</TitleText>
<IconButton
size='small'
@ -238,14 +222,12 @@ export const FeatureStrategyMenuCards = ({
<Box>
<SectionTitle>
<Typography color='inherit' variant='body2'>
Pre-defined strategy types
Standard strategies
</Typography>
<Tooltip
title='Select a starting setup, and customize the strategy to your need with targeting and variants'
arrow
>
<StyledInfoIcon />
</Tooltip>
<HelpIcon
tooltip='Select a starting setup, then customize your strategy with targeting and variants'
size='16px'
/>
</SectionTitle>
<GridSection>
<CardWrapper key={defaultStrategy.name}>
@ -278,12 +260,10 @@ export const FeatureStrategyMenuCards = ({
<Typography color='inherit' variant='body2'>
Custom strategies
</Typography>
<Tooltip
title='Custom strategies you have defined in Unleash'
arrow
>
<StyledInfoIcon />
</Tooltip>
<HelpIcon
tooltip='Custom strategies you have defined in Unleash'
size='16px'
/>
</SectionTitle>
<GridSection>
{customStrategies.map((strategy) => (

View File

@ -2,6 +2,8 @@ import type { FC } from 'react';
import { styled, Link } from '@mui/material';
import type { Link as RouterLink } from 'react-router-dom';
import { RELEASE_TEMPLATE_FEEDBACK } from 'constants/links';
import { useUiFlag } from 'hooks/useUiFlag';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
const StyledLink = styled(Link<typeof RouterLink | 'a'>)(({ theme }) => ({
display: 'flex',
@ -13,14 +15,23 @@ const StyledLink = styled(Link<typeof RouterLink | 'a'>)(({ theme }) => ({
marginRight: 'auto',
}));
export const ReleaseTemplatesFeedback: FC = () => (
<StyledLink
component='a'
href={RELEASE_TEMPLATE_FEEDBACK}
underline='hover'
rel='noopener noreferrer'
target='_blank'
>
Give feedback to release templates
</StyledLink>
);
export const ReleaseTemplatesFeedback: FC = () => {
const { isEnterprise } = useUiConfig();
const releaseTemplatesEnabled = useUiFlag('releasePlans');
if (!isEnterprise() || !releaseTemplatesEnabled) {
return null;
}
return (
<StyledLink
component='a'
href={RELEASE_TEMPLATE_FEEDBACK}
underline='hover'
rel='noopener noreferrer'
target='_blank'
>
Give feedback to release templates
</StyledLink>
);
};

View File

@ -95,6 +95,7 @@ export type UiFlags = {
timestampsInChangeRequestTimeline?: boolean;
reportUnknownFlags?: boolean;
lifecycleGraphs?: boolean;
addConfiguration?: boolean;
};
export interface IVersionInfo {

View File

@ -65,7 +65,8 @@ export type IFlagKey =
| 'paygInstanceStatsEvents'
| 'timestampsInChangeRequestTimeline'
| 'lifecycleGraphs'
| 'githubAuth';
| 'githubAuth'
| 'addConfiguration';
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
@ -305,6 +306,10 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_GITHUB_AUTH,
false,
),
addConfiguration: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_ADD_CONFIGURATION,
false,
),
};
export const defaultExperimentalOptions: IExperimentalOptions = {

View File

@ -61,6 +61,7 @@ process.nextTick(async () => {
paygTrialEvents: true,
timestampsInChangeRequestTimeline: true,
lifecycleGraphs: true,
addConfiguration: true,
},
},
authentication: {