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:
parent
0a9d6437c5
commit
15449e83d3
@ -27,7 +27,10 @@ const CardContent = styled('div')(({ theme }) => ({
|
|||||||
|
|
||||||
const HoverButtonsContainer = styled('div')(({ theme }) => ({
|
const HoverButtonsContainer = styled('div')(({ theme }) => ({
|
||||||
position: 'absolute',
|
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',
|
display: 'flex',
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
|
@ -75,9 +75,10 @@ export const FeatureStrategyMenu = ({
|
|||||||
usePendingChangeRequests(projectId);
|
usePendingChangeRequests(projectId);
|
||||||
const { refetch } = useReleasePlans(projectId, featureId, environmentId);
|
const { refetch } = useReleasePlans(projectId, featureId, environmentId);
|
||||||
const { addReleasePlanToFeature } = useReleasePlansApi();
|
const { addReleasePlanToFeature } = useReleasePlansApi();
|
||||||
const { isOss } = useUiConfig();
|
const { isEnterprise } = useUiConfig();
|
||||||
|
const addConfigurationEnabled = useUiFlag('addConfiguration');
|
||||||
const releasePlansEnabled = useUiFlag('releasePlans');
|
const releasePlansEnabled = useUiFlag('releasePlans');
|
||||||
const displayReleasePlanButton = !isOss() && releasePlansEnabled;
|
const displayReleasePlanButton = isEnterprise() && releasePlansEnabled;
|
||||||
const crProtected =
|
const crProtected =
|
||||||
releasePlansEnabled && isChangeRequestConfigured(environmentId);
|
releasePlansEnabled && isChangeRequestConfigured(environmentId);
|
||||||
|
|
||||||
@ -161,6 +162,25 @@ export const FeatureStrategyMenu = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledStrategyMenu onClick={(event) => event.stopPropagation()}>
|
<StyledStrategyMenu onClick={(event) => event.stopPropagation()}>
|
||||||
|
{addConfigurationEnabled ? (
|
||||||
|
<PermissionButton
|
||||||
|
data-testid='ADD_STRATEGY_BUTTON'
|
||||||
|
permission={CREATE_FEATURE_STRATEGY}
|
||||||
|
projectId={projectId}
|
||||||
|
environmentId={environmentId}
|
||||||
|
onClick={openMoreStrategies}
|
||||||
|
aria-labelledby={dialogId}
|
||||||
|
variant={variant}
|
||||||
|
sx={{ minWidth: matchWidth ? '282px' : 'auto' }}
|
||||||
|
disabled={Boolean(disableReason)}
|
||||||
|
tooltipProps={{
|
||||||
|
title: disableReason ? disableReason : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add configuration
|
||||||
|
</PermissionButton>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
{displayReleasePlanButton ? (
|
{displayReleasePlanButton ? (
|
||||||
<PermissionButton
|
<PermissionButton
|
||||||
data-testid='ADD_TEMPLATE_BUTTON'
|
data-testid='ADD_TEMPLATE_BUTTON'
|
||||||
@ -173,7 +193,9 @@ export const FeatureStrategyMenu = ({
|
|||||||
sx={{ minWidth: matchWidth ? '282px' : 'auto' }}
|
sx={{ minWidth: matchWidth ? '282px' : 'auto' }}
|
||||||
disabled={Boolean(disableReason)}
|
disabled={Boolean(disableReason)}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
title: disableReason ? disableReason : undefined,
|
title: disableReason
|
||||||
|
? disableReason
|
||||||
|
: undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Use template
|
Use template
|
||||||
@ -206,11 +228,15 @@ export const FeatureStrategyMenu = ({
|
|||||||
hideLockIcon
|
hideLockIcon
|
||||||
disabled={Boolean(disableReason)}
|
disabled={Boolean(disableReason)}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
title: disableReason ? disableReason : 'More strategies',
|
title: disableReason
|
||||||
|
? disableReason
|
||||||
|
: 'More strategies',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MoreVert />
|
<MoreVert />
|
||||||
</StyledAdditionalMenuButton>
|
</StyledAdditionalMenuButton>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isStrategyMenuDialogOpen}
|
open={isStrategyMenuDialogOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import {
|
import { Link, styled, Typography, Box, IconButton } from '@mui/material';
|
||||||
Link,
|
|
||||||
styled,
|
|
||||||
Typography,
|
|
||||||
Box,
|
|
||||||
IconButton,
|
|
||||||
Tooltip,
|
|
||||||
} 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 { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates';
|
import { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates';
|
||||||
@ -13,10 +6,10 @@ import { FeatureReleasePlanCard } from '../FeatureReleasePlanCard/FeatureRelease
|
|||||||
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
|
import type { IReleasePlanTemplate } from 'interfaces/releasePlans';
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 FactCheckOutlinedIcon from '@mui/icons-material/FactCheckOutlined';
|
import FactCheckOutlinedIcon from '@mui/icons-material/FactCheckOutlined';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig.ts';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig.ts';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||||
|
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon.tsx';
|
||||||
|
|
||||||
interface IFeatureStrategyMenuCardsProps {
|
interface IFeatureStrategyMenuCardsProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -78,11 +71,6 @@ const SectionTitle = styled(Box)(({ theme }) => ({
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledInfoIcon = styled(InfoOutlinedIcon)(({ theme }) => ({
|
|
||||||
fontSize: theme.typography.body2.fontSize,
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledIcon = styled('span')(({ theme }) => ({
|
const StyledIcon = styled('span')(({ theme }) => ({
|
||||||
width: theme.spacing(3),
|
width: theme.spacing(3),
|
||||||
'& > svg': {
|
'& > svg': {
|
||||||
@ -162,8 +150,18 @@ export const FeatureStrategyMenuCards = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!templates.length) {
|
|
||||||
return (
|
return (
|
||||||
|
<Box>
|
||||||
|
<SectionTitle>
|
||||||
|
<Typography color='inherit' variant='body2'>
|
||||||
|
Release templates
|
||||||
|
</Typography>
|
||||||
|
<HelpIcon
|
||||||
|
tooltip='Use a predefined template to roll out features to users'
|
||||||
|
size='16px'
|
||||||
|
/>
|
||||||
|
</SectionTitle>
|
||||||
|
{!templates.length ? (
|
||||||
<EmptyStateContainer>
|
<EmptyStateContainer>
|
||||||
<EmptyStateTitle>
|
<EmptyStateTitle>
|
||||||
<StyledIcon>
|
<StyledIcon>
|
||||||
@ -182,22 +180,7 @@ export const FeatureStrategyMenuCards = ({
|
|||||||
</ClickableBoldText>
|
</ClickableBoldText>
|
||||||
</EmptyStateDescription>
|
</EmptyStateDescription>
|
||||||
</EmptyStateContainer>
|
</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>
|
<GridSection>
|
||||||
{templates.map((template) => (
|
{templates.map((template) => (
|
||||||
<CardWrapper key={template.id}>
|
<CardWrapper key={template.id}>
|
||||||
@ -211,6 +194,7 @@ export const FeatureStrategyMenuCards = ({
|
|||||||
</CardWrapper>
|
</CardWrapper>
|
||||||
))}
|
))}
|
||||||
</GridSection>
|
</GridSection>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -219,7 +203,7 @@ export const FeatureStrategyMenuCards = ({
|
|||||||
<GridContainer>
|
<GridContainer>
|
||||||
<TitleRow>
|
<TitleRow>
|
||||||
<TitleText variant='h2'>
|
<TitleText variant='h2'>
|
||||||
{onlyReleasePlans ? 'Select template' : 'Select strategy'}
|
{onlyReleasePlans ? 'Select template' : 'Add configuration'}
|
||||||
</TitleText>
|
</TitleText>
|
||||||
<IconButton
|
<IconButton
|
||||||
size='small'
|
size='small'
|
||||||
@ -238,14 +222,12 @@ export const FeatureStrategyMenuCards = ({
|
|||||||
<Box>
|
<Box>
|
||||||
<SectionTitle>
|
<SectionTitle>
|
||||||
<Typography color='inherit' variant='body2'>
|
<Typography color='inherit' variant='body2'>
|
||||||
Pre-defined strategy types
|
Standard strategies
|
||||||
</Typography>
|
</Typography>
|
||||||
<Tooltip
|
<HelpIcon
|
||||||
title='Select a starting setup, and customize the strategy to your need with targeting and variants'
|
tooltip='Select a starting setup, then customize your strategy with targeting and variants'
|
||||||
arrow
|
size='16px'
|
||||||
>
|
/>
|
||||||
<StyledInfoIcon />
|
|
||||||
</Tooltip>
|
|
||||||
</SectionTitle>
|
</SectionTitle>
|
||||||
<GridSection>
|
<GridSection>
|
||||||
<CardWrapper key={defaultStrategy.name}>
|
<CardWrapper key={defaultStrategy.name}>
|
||||||
@ -278,12 +260,10 @@ export const FeatureStrategyMenuCards = ({
|
|||||||
<Typography color='inherit' variant='body2'>
|
<Typography color='inherit' variant='body2'>
|
||||||
Custom strategies
|
Custom strategies
|
||||||
</Typography>
|
</Typography>
|
||||||
<Tooltip
|
<HelpIcon
|
||||||
title='Custom strategies you have defined in Unleash'
|
tooltip='Custom strategies you have defined in Unleash'
|
||||||
arrow
|
size='16px'
|
||||||
>
|
/>
|
||||||
<StyledInfoIcon />
|
|
||||||
</Tooltip>
|
|
||||||
</SectionTitle>
|
</SectionTitle>
|
||||||
<GridSection>
|
<GridSection>
|
||||||
{customStrategies.map((strategy) => (
|
{customStrategies.map((strategy) => (
|
||||||
|
@ -2,6 +2,8 @@ import type { FC } from 'react';
|
|||||||
import { styled, Link } from '@mui/material';
|
import { styled, Link } from '@mui/material';
|
||||||
import type { Link as RouterLink } from 'react-router-dom';
|
import type { Link as RouterLink } from 'react-router-dom';
|
||||||
import { RELEASE_TEMPLATE_FEEDBACK } from 'constants/links';
|
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 }) => ({
|
const StyledLink = styled(Link<typeof RouterLink | 'a'>)(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -13,7 +15,15 @@ const StyledLink = styled(Link<typeof RouterLink | 'a'>)(({ theme }) => ({
|
|||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const ReleaseTemplatesFeedback: FC = () => (
|
export const ReleaseTemplatesFeedback: FC = () => {
|
||||||
|
const { isEnterprise } = useUiConfig();
|
||||||
|
const releaseTemplatesEnabled = useUiFlag('releasePlans');
|
||||||
|
|
||||||
|
if (!isEnterprise() || !releaseTemplatesEnabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<StyledLink
|
<StyledLink
|
||||||
component='a'
|
component='a'
|
||||||
href={RELEASE_TEMPLATE_FEEDBACK}
|
href={RELEASE_TEMPLATE_FEEDBACK}
|
||||||
@ -23,4 +33,5 @@ export const ReleaseTemplatesFeedback: FC = () => (
|
|||||||
>
|
>
|
||||||
Give feedback to release templates
|
Give feedback to release templates
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
@ -95,6 +95,7 @@ export type UiFlags = {
|
|||||||
timestampsInChangeRequestTimeline?: boolean;
|
timestampsInChangeRequestTimeline?: boolean;
|
||||||
reportUnknownFlags?: boolean;
|
reportUnknownFlags?: boolean;
|
||||||
lifecycleGraphs?: boolean;
|
lifecycleGraphs?: boolean;
|
||||||
|
addConfiguration?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IVersionInfo {
|
export interface IVersionInfo {
|
||||||
|
@ -65,7 +65,8 @@ export type IFlagKey =
|
|||||||
| 'paygInstanceStatsEvents'
|
| 'paygInstanceStatsEvents'
|
||||||
| 'timestampsInChangeRequestTimeline'
|
| 'timestampsInChangeRequestTimeline'
|
||||||
| 'lifecycleGraphs'
|
| 'lifecycleGraphs'
|
||||||
| 'githubAuth';
|
| 'githubAuth'
|
||||||
|
| 'addConfiguration';
|
||||||
|
|
||||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||||
|
|
||||||
@ -305,6 +306,10 @@ const flags: IFlags = {
|
|||||||
process.env.UNLEASH_EXPERIMENTAL_GITHUB_AUTH,
|
process.env.UNLEASH_EXPERIMENTAL_GITHUB_AUTH,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
addConfiguration: parseEnvVarBoolean(
|
||||||
|
process.env.UNLEASH_EXPERIMENTAL_ADD_CONFIGURATION,
|
||||||
|
false,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultExperimentalOptions: IExperimentalOptions = {
|
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||||
|
@ -61,6 +61,7 @@ process.nextTick(async () => {
|
|||||||
paygTrialEvents: true,
|
paygTrialEvents: true,
|
||||||
timestampsInChangeRequestTimeline: true,
|
timestampsInChangeRequestTimeline: true,
|
||||||
lifecycleGraphs: true,
|
lifecycleGraphs: true,
|
||||||
|
addConfiguration: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authentication: {
|
authentication: {
|
||||||
|
Loading…
Reference in New Issue
Block a user