1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-24 01:18:01 +02:00

chore: use new designs for project default strategies (#9447)

Implements the new strategy list design for default strategies. Moves
the old impl into a legacy file. Also: removes the description from the
strategy item. From my digging, we only showed this for default strategy
items and it didn't really provide any useful information. The only
other place you can add a description is for custom strategies (at least
that I could find), but these are deprecated and we never show the
description when you apply the strategy anyway.

Rendered:

![image](https://github.com/user-attachments/assets/fb244f10-0a19-42f5-a517-090d6703c76a)

Without the flag (nothing changes):

![image](https://github.com/user-attachments/assets/62159338-8b26-4cb2-825d-e20965c6558d)
This commit is contained in:
Thomas Heartman 2025-03-10 16:09:32 +01:00 committed by GitHub
parent c26c040fc1
commit 7dd89034aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 338 additions and 231 deletions

View File

@ -35,14 +35,8 @@ test('should render strategy name, custom title and description', async () => {
parameters: {},
};
render(
<StrategyItemContainer
strategy={strategy}
description={'description'}
/>,
);
render(<StrategyItemContainer strategy={strategy} />);
expect(screen.getByText('strategy name:')).toBeInTheDocument();
expect(screen.getByText('description')).toBeInTheDocument();
await screen.findByText('custom title'); // behind async flag
});

View File

@ -12,13 +12,12 @@ import { Truncator } from '../Truncator/Truncator';
type StrategyItemContainerProps = {
strategyHeaderLevel?: 1 | 2 | 3 | 4 | 5 | 6;
strategy: IFeatureStrategy | PlaygroundStrategySchema;
strategy: Omit<IFeatureStrategy, 'id'> | PlaygroundStrategySchema;
onDragStart?: DragEventHandler<HTMLButtonElement>;
onDragEnd?: DragEventHandler<HTMLButtonElement>;
headerItemsRight?: ReactNode;
className?: string;
style?: React.CSSProperties;
description?: string;
children?: React.ReactNode;
};
@ -28,23 +27,6 @@ const DragIcon = styled(IconButton)({
transition: 'color 0.2s ease-in-out',
});
const StyledDescription = styled('div')(({ theme }) => ({
fontSize: theme.typography.fontSize,
fontWeight: 'normal',
color: theme.palette.text.secondary,
display: 'none',
top: theme.spacing(2.5),
[theme.breakpoints.up('md')]: {
display: 'block',
},
}));
const StyledCustomTitle = styled('div')(({ theme }) => ({
fontWeight: 'normal',
display: 'none',
[theme.breakpoints.up('md')]: {
display: 'block',
},
}));
const StyledHeaderContainer = styled('hgroup')(({ theme }) => ({
display: 'flex',
flexFlow: 'row nowrap',
@ -66,7 +48,7 @@ const StyledTruncator = styled(Truncator)(({ theme }) => ({
margin: 0,
}));
const NewStyledHeader = styled('div', {
const StyledHeader = styled('div', {
shouldForwardProp: (prop) => prop !== 'draggable' && prop !== 'disabled',
})<{ draggable: boolean; disabled: boolean }>(
({ theme, draggable, disabled }) => ({
@ -89,7 +71,6 @@ export const StrategyItemContainer: FC<StrategyItemContainerProps> = ({
strategyHeaderLevel = 3,
children,
style = {},
description,
}) => {
const StrategyHeaderLink: React.FC<{ children?: React.ReactNode }> =
'links' in strategy // todo: revisit this when we get to playground, related to flag `flagOverviewRedesign`
@ -99,7 +80,7 @@ export const StrategyItemContainer: FC<StrategyItemContainerProps> = ({
return (
<Box sx={{ position: 'relative' }}>
<StyledContainer style={style}>
<NewStyledHeader
<StyledHeader
draggable={Boolean(onDragStart)}
disabled={Boolean(strategy?.disabled)}
>
@ -146,25 +127,12 @@ export const StrategyItemContainer: FC<StrategyItemContainerProps> = ({
{formatStrategyName(String(strategy.name))}
</Typography>
)}
<ConditionallyRender
condition={Boolean(description)}
show={
<StyledDescription>
{description}
</StyledDescription>
}
/>
</StyledHeaderContainer>
</StrategyHeaderLink>
<ConditionallyRender
condition={Boolean(strategy?.disabled)}
show={() => (
<>
<Badge color='disabled'>Disabled</Badge>
</>
)}
/>
{strategy.disabled ? (
<Badge color='disabled'>Disabled</Badge>
) : null}
<Box
sx={{
marginLeft: 'auto',
@ -175,7 +143,7 @@ export const StrategyItemContainer: FC<StrategyItemContainerProps> = ({
>
{headerItemsRight}
</Box>
</NewStyledHeader>
</StyledHeader>
<Box sx={{ p: 2, pt: 0 }}>{children}</Box>
</StyledContainer>
</Box>

View File

@ -3,11 +3,11 @@ import type { IFeatureStrategy } from 'interfaces/strategy';
import { StrategyExecution } from './StrategyExecution/StrategyExecution';
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';
import { Box } from '@mui/material';
import { StrategyItemContainer as NewStrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
type StrategyItemProps = {
headerItemsRight?: ReactNode;
strategy: IFeatureStrategy;
strategy: Omit<IFeatureStrategy, 'id'>;
onDragStart?: DragEventHandler<HTMLButtonElement>;
onDragEnd?: DragEventHandler<HTMLButtonElement>;
strategyHeaderLevel?: 1 | 2 | 3 | 4 | 5 | 6;
@ -21,7 +21,7 @@ export const StrategyItem: FC<StrategyItemProps> = ({
strategyHeaderLevel,
}) => {
return (
<NewStrategyItemContainer
<StrategyItemContainer
strategyHeaderLevel={strategyHeaderLevel}
strategy={strategy}
onDragStart={onDragStart}
@ -39,6 +39,6 @@ export const StrategyItem: FC<StrategyItemProps> = ({
) : (
<SplitPreviewSlider variants={strategy.variants} />
))}
</NewStrategyItemContainer>
</StrategyItemContainer>
);
};

View File

@ -1,4 +1,4 @@
import type { FC, ReactNode } from 'react';
import type { FC, PropsWithChildren } from 'react';
import {
AccordionSummary,
type AccordionSummaryProps,
@ -6,6 +6,7 @@ import {
} from '@mui/material';
import ExpandMore from '@mui/icons-material/ExpandMore';
import { Truncator } from 'component/common/Truncator/Truncator';
import { useId } from 'hooks/useId';
const StyledAccordionSummary = styled(AccordionSummary, {
shouldForwardProp: (prop) => prop !== 'expandable',
@ -47,22 +48,19 @@ const StyledHeaderTitleLabel = styled('p')(({ theme }) => ({
}));
const StyledTruncator = styled(Truncator)(({ theme }) => ({
fontSize: theme.typography.body1.fontSize,
fontSize: theme.typography.h2.fontSize,
fontWeight: theme.typography.fontWeightMedium,
}));
type EnvironmentHeaderProps = {
environmentId: string;
children: ReactNode;
expandable?: boolean;
} & AccordionSummaryProps;
export const EnvironmentHeader: FC<EnvironmentHeaderProps> = ({
environmentId,
children,
expandable = true,
...props
}) => {
export const EnvironmentHeader: FC<
PropsWithChildren<EnvironmentHeaderProps>
> = ({ environmentId, children, expandable = true, ...props }) => {
const id = useId();
return (
<StyledAccordionSummary
{...props}
@ -71,6 +69,8 @@ export const EnvironmentHeader: FC<EnvironmentHeaderProps> = ({
sx={{ visibility: expandable ? 'visible' : 'hidden' }}
/>
}
id={id}
aria-controls={`environment-accordion-${id}-content`}
expandable={expandable}
>
<StyledHeader data-loading>

View File

@ -10,13 +10,15 @@ import {
UPDATE_PROJECT,
} from 'component/providers/AccessProvider/permissions';
import { Alert, styled } from '@mui/material';
import ProjectEnvironment from './ProjectEnvironment/ProjectEnvironment';
import LegacyProjectEnvironment from './ProjectEnvironment/LegacyProjectEnvironment';
import { Route, Routes, useNavigate } from 'react-router-dom';
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
import EditDefaultStrategy from './ProjectEnvironment/ProjectEnvironmentDefaultStrategy/EditDefaultStrategy';
import useProjectOverview, {
useProjectOverviewNameOrId,
} from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import { useUiFlag } from 'hooks/useUiFlag';
import { ProjectEnvironment } from './ProjectEnvironment/ProjectEnvironment';
const StyledAlert = styled(Alert)(({ theme }) => ({
marginBottom: theme.spacing(4),
@ -28,6 +30,7 @@ export const ProjectDefaultStrategySettings = () => {
const { project } = useProjectOverview(projectId);
const navigate = useNavigate();
usePageTitle(`Project default strategy configuration ${projectName}`);
const flagOverviewRedesign = useUiFlag('flagOverviewRedesign');
if (
!hasAccess(
@ -61,12 +64,19 @@ export const ProjectDefaultStrategySettings = () => {
specific environment. These will be used when you enable a
toggle environment that has no strategies defined
</StyledAlert>
{project?.environments.map((environment) => (
<ProjectEnvironment
environment={environment}
key={environment.environment}
/>
))}
{project?.environments.map((environment) =>
flagOverviewRedesign ? (
<ProjectEnvironment
environment={environment}
key={environment.environment}
/>
) : (
<LegacyProjectEnvironment
environment={environment}
key={environment.environment}
/>
),
)}
</PageContent>
<Routes>
<Route

View File

@ -0,0 +1,149 @@
import {
Accordion,
AccordionDetails,
AccordionSummary,
styled,
useTheme,
} from '@mui/material';
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
import { PROJECT_ENVIRONMENT_ACCORDION } from 'utils/testIds';
import type { ProjectEnvironmentType } from '../../../../../../interfaces/environments';
import LegacyProjectEnvironmentDefaultStrategy from './ProjectEnvironmentDefaultStrategy/LegacyProjectEnvironmentDefaultStrategy';
interface IProjectEnvironmentProps {
environment: ProjectEnvironmentType;
}
const StyledProjectEnvironmentOverview = styled('div', {
shouldForwardProp: (prop) => prop !== 'enabled',
})<{ enabled: boolean }>(({ theme, enabled }) => ({
borderRadius: theme.shape.borderRadiusLarge,
marginBottom: theme.spacing(2),
backgroundColor: enabled
? theme.palette.background.paper
: theme.palette.envAccordion.disabled,
}));
const StyledAccordion = styled(Accordion)({
boxShadow: 'none',
background: 'none',
});
const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
boxShadow: 'none',
padding: theme.spacing(2, 4),
pointerEvents: 'none',
[theme.breakpoints.down(400)]: {
padding: theme.spacing(1, 2),
},
}));
const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
padding: theme.spacing(3),
background: theme.palette.envAccordion.expanded,
borderBottomLeftRadius: theme.shape.borderRadiusLarge,
borderBottomRightRadius: theme.shape.borderRadiusLarge,
boxShadow: theme.boxShadows.accordionFooter,
[theme.breakpoints.down('md')]: {
padding: theme.spacing(2, 1),
},
}));
const StyledAccordionBody = styled('div')(({ theme }) => ({
width: '100%',
position: 'relative',
paddingBottom: theme.spacing(2),
}));
const StyledAccordionBodyInnerContainer = styled('div')(({ theme }) => ({
[theme.breakpoints.down(400)]: {
padding: theme.spacing(1),
},
}));
const StyledHeader = styled('div', {
shouldForwardProp: (prop) => prop !== 'enabled',
})<{ enabled: boolean }>(({ theme, enabled }) => ({
display: 'flex',
justifyContent: 'center',
flexDirection: 'column',
color: enabled ? theme.palette.text.primary : theme.palette.text.secondary,
}));
const StyledHeaderTitle = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
fontWeight: 'bold',
[theme.breakpoints.down(560)]: {
flexDirection: 'column',
textAlign: 'center',
},
}));
const StyledEnvironmentIcon = styled(EnvironmentIcon)(({ theme }) => ({
[theme.breakpoints.down(560)]: {
marginBottom: '0.5rem',
},
}));
const StyledStringTruncator = styled(StringTruncator)(({ theme }) => ({
fontSize: theme.fontSizes.bodySize,
fontWeight: theme.typography.fontWeightMedium,
[theme.breakpoints.down(560)]: {
textAlign: 'center',
},
}));
const ProjectEnvironment = ({ environment }: IProjectEnvironmentProps) => {
const { environment: name } = environment;
const description = `Default strategy configuration in the ${name} environment`;
const theme = useTheme();
const enabled = false;
return (
<StyledProjectEnvironmentOverview enabled={false}>
<StyledAccordion
expanded={true}
onChange={(e) => e.stopPropagation()}
data-testid={`${PROJECT_ENVIRONMENT_ACCORDION}_${name}`}
className={`environment-accordion ${
enabled ? '' : 'accordion-disabled'
}`}
style={{
outline: `2px solid ${theme.palette.divider}`,
backgroundColor: theme.palette.background.paper,
}}
>
<StyledAccordionSummary>
<StyledHeader data-loading enabled={enabled}>
<StyledHeaderTitle>
<StyledEnvironmentIcon enabled />
<div>
<StyledStringTruncator
text={name}
maxWidth='100'
maxLength={15}
/>
</div>
</StyledHeaderTitle>
</StyledHeader>
</StyledAccordionSummary>
<StyledAccordionDetails>
<StyledAccordionBody>
<StyledAccordionBodyInnerContainer>
<LegacyProjectEnvironmentDefaultStrategy
environment={environment}
description={description}
/>
</StyledAccordionBodyInnerContainer>
</StyledAccordionBody>
</StyledAccordionDetails>
</StyledAccordion>
</StyledProjectEnvironmentOverview>
);
};
export default ProjectEnvironment;

View File

@ -1,150 +1,47 @@
import {
Accordion,
AccordionDetails,
AccordionSummary,
styled,
useTheme,
} from '@mui/material';
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
import { Accordion, AccordionDetails, styled } from '@mui/material';
import { PROJECT_ENVIRONMENT_ACCORDION } from 'utils/testIds';
import type { ProjectEnvironmentType } from '../../../../../../interfaces/environments';
import ProjectEnvironmentDefaultStrategy from './ProjectEnvironmentDefaultStrategy/ProjectEnvironmentDefaultStrategy';
import { ProjectEnvironmentDefaultStrategy } from './ProjectEnvironmentDefaultStrategy/ProjectEnvironmentDefaultStrategy';
import { EnvironmentHeader } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentHeader/EnvironmentHeader';
interface IProjectEnvironmentProps {
environment: ProjectEnvironmentType;
}
const StyledProjectEnvironmentOverview = styled('div', {
shouldForwardProp: (prop) => prop !== 'enabled',
})<{ enabled: boolean }>(({ theme, enabled }) => ({
borderRadius: theme.shape.borderRadiusLarge,
const StyledProjectEnvironmentOverview = styled('div')(({ theme }) => ({
marginBottom: theme.spacing(2),
backgroundColor: enabled
? theme.palette.background.paper
: theme.palette.envAccordion.disabled,
}));
const StyledAccordion = styled(Accordion)({
const StyledAccordion = styled(Accordion)(({ theme }) => ({
boxShadow: 'none',
background: 'none',
});
const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
boxShadow: 'none',
padding: theme.spacing(2, 4),
pointerEvents: 'none',
[theme.breakpoints.down(400)]: {
padding: theme.spacing(1, 2),
},
border: `1px solid ${theme.palette.divider}`,
overflow: 'hidden',
}));
const StyledAccordionDetails = styled(AccordionDetails, {
shouldForwardProp: (prop) => prop !== 'enabled',
})<{ enabled: boolean }>(({ theme }) => ({
padding: theme.spacing(3),
background: theme.palette.envAccordion.expanded,
borderBottomLeftRadius: theme.shape.borderRadiusLarge,
borderBottomRightRadius: theme.shape.borderRadiusLarge,
boxShadow: theme.boxShadows.accordionFooter,
[theme.breakpoints.down('md')]: {
padding: theme.spacing(2, 1),
},
const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
padding: 0,
background: theme.palette.background.elevation1,
}));
const StyledAccordionBody = styled('div')(({ theme }) => ({
width: '100%',
position: 'relative',
paddingBottom: theme.spacing(2),
}));
const StyledAccordionBodyInnerContainer = styled('div')(({ theme }) => ({
[theme.breakpoints.down(400)]: {
padding: theme.spacing(1),
},
}));
const StyledHeader = styled('div', {
shouldForwardProp: (prop) => prop !== 'enabled',
})<{ enabled: boolean }>(({ theme, enabled }) => ({
display: 'flex',
justifyContent: 'center',
flexDirection: 'column',
color: enabled ? theme.palette.text.primary : theme.palette.text.secondary,
}));
const StyledHeaderTitle = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
fontWeight: 'bold',
[theme.breakpoints.down(560)]: {
flexDirection: 'column',
textAlign: 'center',
},
}));
const StyledEnvironmentIcon = styled(EnvironmentIcon)(({ theme }) => ({
[theme.breakpoints.down(560)]: {
marginBottom: '0.5rem',
},
}));
const StyledStringTruncator = styled(StringTruncator)(({ theme }) => ({
fontSize: theme.fontSizes.bodySize,
fontWeight: theme.typography.fontWeightMedium,
[theme.breakpoints.down(560)]: {
textAlign: 'center',
},
}));
const ProjectEnvironment = ({ environment }: IProjectEnvironmentProps) => {
export const ProjectEnvironment = ({
environment,
}: IProjectEnvironmentProps) => {
const { environment: name } = environment;
const description = `Default strategy configuration in the ${name} environment`;
const theme = useTheme();
const enabled = false;
return (
<StyledProjectEnvironmentOverview enabled={false}>
<StyledProjectEnvironmentOverview>
<StyledAccordion
expanded={true}
onChange={(e) => e.stopPropagation()}
data-testid={`${PROJECT_ENVIRONMENT_ACCORDION}_${name}`}
className={`environment-accordion ${
enabled ? '' : 'accordion-disabled'
}`}
style={{
outline: `2px solid ${theme.palette.divider}`,
backgroundColor: theme.palette.background.paper,
}}
>
<StyledAccordionSummary>
<StyledHeader data-loading enabled={enabled}>
<StyledHeaderTitle>
<StyledEnvironmentIcon enabled />
<div>
<StyledStringTruncator
text={name}
maxWidth='100'
maxLength={15}
/>
</div>
</StyledHeaderTitle>
</StyledHeader>
</StyledAccordionSummary>
<StyledAccordionDetails enabled>
<StyledAccordionBody>
<StyledAccordionBodyInnerContainer>
<ProjectEnvironmentDefaultStrategy
environment={environment}
description={description}
/>
</StyledAccordionBodyInnerContainer>
</StyledAccordionBody>
<EnvironmentHeader environmentId={name} expandable={false} />
<StyledAccordionDetails>
<ProjectEnvironmentDefaultStrategy
environment={environment}
/>
</StyledAccordionDetails>
</StyledAccordion>
</StyledProjectEnvironmentOverview>
);
};
export default ProjectEnvironment;

View File

@ -0,0 +1,94 @@
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { Link } from 'react-router-dom';
import Edit from '@mui/icons-material/Edit';
import { StrategyExecution } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution';
import type { ProjectEnvironmentType } from 'interfaces/environments';
import { useMemo } from 'react';
import type { CreateFeatureStrategySchema } from 'openapi';
import {
PROJECT_DEFAULT_STRATEGY_WRITE,
UPDATE_PROJECT,
} from '@server/types/permissions';
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/LegacyStrategyItemContainer';
interface ProjectEnvironmentDefaultStrategyProps {
environment: ProjectEnvironmentType;
description: string;
}
export const formatEditProjectEnvironmentStrategyPath = (
projectId: string,
environmentId: string,
): string => {
const params = new URLSearchParams({ environmentId });
return `/projects/${projectId}/settings/default-strategy/edit?${params}`;
};
const DEFAULT_STRATEGY: CreateFeatureStrategySchema = {
name: 'flexibleRollout',
disabled: false,
constraints: [],
title: '',
parameters: {
rollout: '100',
stickiness: 'default',
groupId: '',
},
};
const ProjectEnvironmentDefaultStrategy = ({
environment,
description,
}: ProjectEnvironmentDefaultStrategyProps) => {
const projectId = useRequiredPathParam('projectId');
const { environment: environmentId, defaultStrategy } = environment;
const editStrategyPath = formatEditProjectEnvironmentStrategyPath(
projectId,
environmentId,
);
const strategy: CreateFeatureStrategySchema = useMemo(() => {
return defaultStrategy ? defaultStrategy : DEFAULT_STRATEGY;
}, [JSON.stringify(defaultStrategy)]);
return (
<>
<StrategyItemContainer
strategy={strategy as any}
description={description}
actions={
<>
<PermissionIconButton
permission={[
PROJECT_DEFAULT_STRATEGY_WRITE,
UPDATE_PROJECT,
]}
environmentId={environmentId}
projectId={projectId}
component={Link}
to={editStrategyPath}
tooltipProps={{
title: `Edit default strategy for "${environmentId}"`,
}}
data-testid={`STRATEGY_EDIT-${strategy?.name}`}
>
<Edit />
</PermissionIconButton>
</>
}
>
<StrategyExecution strategy={strategy} />
{strategy.variants && strategy.variants.length > 0 ? (
<SplitPreviewSlider variants={strategy.variants} />
) : null}
</StrategyItemContainer>
</>
);
};
export default ProjectEnvironmentDefaultStrategy;

View File

@ -2,7 +2,6 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { Link } from 'react-router-dom';
import Edit from '@mui/icons-material/Edit';
import { StrategyExecution } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution';
import type { ProjectEnvironmentType } from 'interfaces/environments';
import { useMemo } from 'react';
import type { CreateFeatureStrategySchema } from 'openapi';
@ -10,12 +9,11 @@ import {
PROJECT_DEFAULT_STRATEGY_WRITE,
UPDATE_PROJECT,
} from '@server/types/permissions';
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/LegacyStrategyItemContainer';
import { StrategyItem } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem';
import type { IFeatureStrategy } from 'interfaces/strategy';
interface ProjectEnvironmentDefaultStrategyProps {
environment: ProjectEnvironmentType;
description: string;
}
export const formatEditProjectEnvironmentStrategyPath = (
@ -39,9 +37,8 @@ const DEFAULT_STRATEGY: CreateFeatureStrategySchema = {
},
};
const ProjectEnvironmentDefaultStrategy = ({
export const ProjectEnvironmentDefaultStrategy = ({
environment,
description,
}: ProjectEnvironmentDefaultStrategyProps) => {
const projectId = useRequiredPathParam('projectId');
const { environment: environmentId, defaultStrategy } = environment;
@ -51,44 +48,42 @@ const ProjectEnvironmentDefaultStrategy = ({
environmentId,
);
const strategy: CreateFeatureStrategySchema = useMemo(() => {
return defaultStrategy ? defaultStrategy : DEFAULT_STRATEGY;
const strategy: Omit<IFeatureStrategy, 'id'> = useMemo(() => {
const baseDefaultStrategy = defaultStrategy
? defaultStrategy
: DEFAULT_STRATEGY;
return {
...baseDefaultStrategy,
disabled: false,
constraints: baseDefaultStrategy.constraints ?? [],
title: baseDefaultStrategy.title ?? '',
parameters: baseDefaultStrategy.parameters ?? {},
};
}, [JSON.stringify(defaultStrategy)]);
return (
<>
<StrategyItemContainer
strategy={strategy as any}
description={description}
actions={
<>
<PermissionIconButton
permission={[
PROJECT_DEFAULT_STRATEGY_WRITE,
UPDATE_PROJECT,
]}
environmentId={environmentId}
projectId={projectId}
component={Link}
to={editStrategyPath}
tooltipProps={{
title: `Edit default strategy for "${environmentId}"`,
}}
data-testid={`STRATEGY_EDIT-${strategy?.name}`}
>
<Edit />
</PermissionIconButton>
</>
}
>
<StrategyExecution strategy={strategy} />
{strategy.variants && strategy.variants.length > 0 ? (
<SplitPreviewSlider variants={strategy.variants} />
) : null}
</StrategyItemContainer>
</>
<StrategyItem
strategy={strategy}
headerItemsRight={
<>
<PermissionIconButton
permission={[
PROJECT_DEFAULT_STRATEGY_WRITE,
UPDATE_PROJECT,
]}
environmentId={environmentId}
projectId={projectId}
component={Link}
to={editStrategyPath}
tooltipProps={{
title: `Edit default strategy for "${environmentId}"`,
}}
data-testid={`STRATEGY_EDIT-${strategy.name}`}
>
<Edit />
</PermissionIconButton>
</>
}
/>
);
};
export default ProjectEnvironmentDefaultStrategy;