mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-02 01:17:58 +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:  Without the flag (nothing changes): 
This commit is contained in:
parent
c26c040fc1
commit
7dd89034aa
@ -35,14 +35,8 @@ test('should render strategy name, custom title and description', async () => {
|
|||||||
parameters: {},
|
parameters: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
render(
|
render(<StrategyItemContainer strategy={strategy} />);
|
||||||
<StrategyItemContainer
|
|
||||||
strategy={strategy}
|
|
||||||
description={'description'}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByText('strategy name:')).toBeInTheDocument();
|
expect(screen.getByText('strategy name:')).toBeInTheDocument();
|
||||||
expect(screen.getByText('description')).toBeInTheDocument();
|
|
||||||
await screen.findByText('custom title'); // behind async flag
|
await screen.findByText('custom title'); // behind async flag
|
||||||
});
|
});
|
||||||
|
@ -12,13 +12,12 @@ import { Truncator } from '../Truncator/Truncator';
|
|||||||
|
|
||||||
type StrategyItemContainerProps = {
|
type StrategyItemContainerProps = {
|
||||||
strategyHeaderLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
strategyHeaderLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
||||||
strategy: IFeatureStrategy | PlaygroundStrategySchema;
|
strategy: Omit<IFeatureStrategy, 'id'> | PlaygroundStrategySchema;
|
||||||
onDragStart?: DragEventHandler<HTMLButtonElement>;
|
onDragStart?: DragEventHandler<HTMLButtonElement>;
|
||||||
onDragEnd?: DragEventHandler<HTMLButtonElement>;
|
onDragEnd?: DragEventHandler<HTMLButtonElement>;
|
||||||
headerItemsRight?: ReactNode;
|
headerItemsRight?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
description?: string;
|
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,23 +27,6 @@ const DragIcon = styled(IconButton)({
|
|||||||
transition: 'color 0.2s ease-in-out',
|
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 }) => ({
|
const StyledHeaderContainer = styled('hgroup')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexFlow: 'row nowrap',
|
flexFlow: 'row nowrap',
|
||||||
@ -66,7 +48,7 @@ const StyledTruncator = styled(Truncator)(({ theme }) => ({
|
|||||||
margin: 0,
|
margin: 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const NewStyledHeader = styled('div', {
|
const StyledHeader = styled('div', {
|
||||||
shouldForwardProp: (prop) => prop !== 'draggable' && prop !== 'disabled',
|
shouldForwardProp: (prop) => prop !== 'draggable' && prop !== 'disabled',
|
||||||
})<{ draggable: boolean; disabled: boolean }>(
|
})<{ draggable: boolean; disabled: boolean }>(
|
||||||
({ theme, draggable, disabled }) => ({
|
({ theme, draggable, disabled }) => ({
|
||||||
@ -89,7 +71,6 @@ export const StrategyItemContainer: FC<StrategyItemContainerProps> = ({
|
|||||||
strategyHeaderLevel = 3,
|
strategyHeaderLevel = 3,
|
||||||
children,
|
children,
|
||||||
style = {},
|
style = {},
|
||||||
description,
|
|
||||||
}) => {
|
}) => {
|
||||||
const StrategyHeaderLink: React.FC<{ children?: React.ReactNode }> =
|
const StrategyHeaderLink: React.FC<{ children?: React.ReactNode }> =
|
||||||
'links' in strategy // todo: revisit this when we get to playground, related to flag `flagOverviewRedesign`
|
'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 (
|
return (
|
||||||
<Box sx={{ position: 'relative' }}>
|
<Box sx={{ position: 'relative' }}>
|
||||||
<StyledContainer style={style}>
|
<StyledContainer style={style}>
|
||||||
<NewStyledHeader
|
<StyledHeader
|
||||||
draggable={Boolean(onDragStart)}
|
draggable={Boolean(onDragStart)}
|
||||||
disabled={Boolean(strategy?.disabled)}
|
disabled={Boolean(strategy?.disabled)}
|
||||||
>
|
>
|
||||||
@ -146,25 +127,12 @@ export const StrategyItemContainer: FC<StrategyItemContainerProps> = ({
|
|||||||
{formatStrategyName(String(strategy.name))}
|
{formatStrategyName(String(strategy.name))}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
<ConditionallyRender
|
|
||||||
condition={Boolean(description)}
|
|
||||||
show={
|
|
||||||
<StyledDescription>
|
|
||||||
{description}
|
|
||||||
</StyledDescription>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</StyledHeaderContainer>
|
</StyledHeaderContainer>
|
||||||
</StrategyHeaderLink>
|
</StrategyHeaderLink>
|
||||||
|
|
||||||
<ConditionallyRender
|
{strategy.disabled ? (
|
||||||
condition={Boolean(strategy?.disabled)}
|
<Badge color='disabled'>Disabled</Badge>
|
||||||
show={() => (
|
) : null}
|
||||||
<>
|
|
||||||
<Badge color='disabled'>Disabled</Badge>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
@ -175,7 +143,7 @@ export const StrategyItemContainer: FC<StrategyItemContainerProps> = ({
|
|||||||
>
|
>
|
||||||
{headerItemsRight}
|
{headerItemsRight}
|
||||||
</Box>
|
</Box>
|
||||||
</NewStyledHeader>
|
</StyledHeader>
|
||||||
<Box sx={{ p: 2, pt: 0 }}>{children}</Box>
|
<Box sx={{ p: 2, pt: 0 }}>{children}</Box>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -3,11 +3,11 @@ import type { IFeatureStrategy } from 'interfaces/strategy';
|
|||||||
import { StrategyExecution } from './StrategyExecution/StrategyExecution';
|
import { StrategyExecution } from './StrategyExecution/StrategyExecution';
|
||||||
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';
|
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';
|
||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import { StrategyItemContainer as NewStrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
|
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
|
||||||
|
|
||||||
type StrategyItemProps = {
|
type StrategyItemProps = {
|
||||||
headerItemsRight?: ReactNode;
|
headerItemsRight?: ReactNode;
|
||||||
strategy: IFeatureStrategy;
|
strategy: Omit<IFeatureStrategy, 'id'>;
|
||||||
onDragStart?: DragEventHandler<HTMLButtonElement>;
|
onDragStart?: DragEventHandler<HTMLButtonElement>;
|
||||||
onDragEnd?: DragEventHandler<HTMLButtonElement>;
|
onDragEnd?: DragEventHandler<HTMLButtonElement>;
|
||||||
strategyHeaderLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
strategyHeaderLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
||||||
@ -21,7 +21,7 @@ export const StrategyItem: FC<StrategyItemProps> = ({
|
|||||||
strategyHeaderLevel,
|
strategyHeaderLevel,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<NewStrategyItemContainer
|
<StrategyItemContainer
|
||||||
strategyHeaderLevel={strategyHeaderLevel}
|
strategyHeaderLevel={strategyHeaderLevel}
|
||||||
strategy={strategy}
|
strategy={strategy}
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
@ -39,6 +39,6 @@ export const StrategyItem: FC<StrategyItemProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<SplitPreviewSlider variants={strategy.variants} />
|
<SplitPreviewSlider variants={strategy.variants} />
|
||||||
))}
|
))}
|
||||||
</NewStrategyItemContainer>
|
</StrategyItemContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { FC, ReactNode } from 'react';
|
import type { FC, PropsWithChildren } from 'react';
|
||||||
import {
|
import {
|
||||||
AccordionSummary,
|
AccordionSummary,
|
||||||
type AccordionSummaryProps,
|
type AccordionSummaryProps,
|
||||||
@ -6,6 +6,7 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||||
import { Truncator } from 'component/common/Truncator/Truncator';
|
import { Truncator } from 'component/common/Truncator/Truncator';
|
||||||
|
import { useId } from 'hooks/useId';
|
||||||
|
|
||||||
const StyledAccordionSummary = styled(AccordionSummary, {
|
const StyledAccordionSummary = styled(AccordionSummary, {
|
||||||
shouldForwardProp: (prop) => prop !== 'expandable',
|
shouldForwardProp: (prop) => prop !== 'expandable',
|
||||||
@ -47,22 +48,19 @@ const StyledHeaderTitleLabel = styled('p')(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledTruncator = styled(Truncator)(({ theme }) => ({
|
const StyledTruncator = styled(Truncator)(({ theme }) => ({
|
||||||
fontSize: theme.typography.body1.fontSize,
|
fontSize: theme.typography.h2.fontSize,
|
||||||
fontWeight: theme.typography.fontWeightMedium,
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
type EnvironmentHeaderProps = {
|
type EnvironmentHeaderProps = {
|
||||||
environmentId: string;
|
environmentId: string;
|
||||||
children: ReactNode;
|
|
||||||
expandable?: boolean;
|
expandable?: boolean;
|
||||||
} & AccordionSummaryProps;
|
} & AccordionSummaryProps;
|
||||||
|
|
||||||
export const EnvironmentHeader: FC<EnvironmentHeaderProps> = ({
|
export const EnvironmentHeader: FC<
|
||||||
environmentId,
|
PropsWithChildren<EnvironmentHeaderProps>
|
||||||
children,
|
> = ({ environmentId, children, expandable = true, ...props }) => {
|
||||||
expandable = true,
|
const id = useId();
|
||||||
...props
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<StyledAccordionSummary
|
<StyledAccordionSummary
|
||||||
{...props}
|
{...props}
|
||||||
@ -71,6 +69,8 @@ export const EnvironmentHeader: FC<EnvironmentHeaderProps> = ({
|
|||||||
sx={{ visibility: expandable ? 'visible' : 'hidden' }}
|
sx={{ visibility: expandable ? 'visible' : 'hidden' }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
id={id}
|
||||||
|
aria-controls={`environment-accordion-${id}-content`}
|
||||||
expandable={expandable}
|
expandable={expandable}
|
||||||
>
|
>
|
||||||
<StyledHeader data-loading>
|
<StyledHeader data-loading>
|
||||||
|
@ -10,13 +10,15 @@ import {
|
|||||||
UPDATE_PROJECT,
|
UPDATE_PROJECT,
|
||||||
} from 'component/providers/AccessProvider/permissions';
|
} from 'component/providers/AccessProvider/permissions';
|
||||||
import { Alert, styled } from '@mui/material';
|
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 { Route, Routes, useNavigate } from 'react-router-dom';
|
||||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||||
import EditDefaultStrategy from './ProjectEnvironment/ProjectEnvironmentDefaultStrategy/EditDefaultStrategy';
|
import EditDefaultStrategy from './ProjectEnvironment/ProjectEnvironmentDefaultStrategy/EditDefaultStrategy';
|
||||||
import useProjectOverview, {
|
import useProjectOverview, {
|
||||||
useProjectOverviewNameOrId,
|
useProjectOverviewNameOrId,
|
||||||
} from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
} from 'hooks/api/getters/useProjectOverview/useProjectOverview';
|
||||||
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
import { ProjectEnvironment } from './ProjectEnvironment/ProjectEnvironment';
|
||||||
|
|
||||||
const StyledAlert = styled(Alert)(({ theme }) => ({
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||||
marginBottom: theme.spacing(4),
|
marginBottom: theme.spacing(4),
|
||||||
@ -28,6 +30,7 @@ export const ProjectDefaultStrategySettings = () => {
|
|||||||
const { project } = useProjectOverview(projectId);
|
const { project } = useProjectOverview(projectId);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
usePageTitle(`Project default strategy configuration – ${projectName}`);
|
usePageTitle(`Project default strategy configuration – ${projectName}`);
|
||||||
|
const flagOverviewRedesign = useUiFlag('flagOverviewRedesign');
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!hasAccess(
|
!hasAccess(
|
||||||
@ -61,12 +64,19 @@ export const ProjectDefaultStrategySettings = () => {
|
|||||||
specific environment. These will be used when you enable a
|
specific environment. These will be used when you enable a
|
||||||
toggle environment that has no strategies defined
|
toggle environment that has no strategies defined
|
||||||
</StyledAlert>
|
</StyledAlert>
|
||||||
{project?.environments.map((environment) => (
|
{project?.environments.map((environment) =>
|
||||||
<ProjectEnvironment
|
flagOverviewRedesign ? (
|
||||||
environment={environment}
|
<ProjectEnvironment
|
||||||
key={environment.environment}
|
environment={environment}
|
||||||
/>
|
key={environment.environment}
|
||||||
))}
|
/>
|
||||||
|
) : (
|
||||||
|
<LegacyProjectEnvironment
|
||||||
|
environment={environment}
|
||||||
|
key={environment.environment}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</PageContent>
|
</PageContent>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
|
@ -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;
|
@ -1,150 +1,47 @@
|
|||||||
import {
|
import { Accordion, AccordionDetails, styled } from '@mui/material';
|
||||||
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 { PROJECT_ENVIRONMENT_ACCORDION } from 'utils/testIds';
|
||||||
import type { ProjectEnvironmentType } from '../../../../../../interfaces/environments';
|
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 {
|
interface IProjectEnvironmentProps {
|
||||||
environment: ProjectEnvironmentType;
|
environment: ProjectEnvironmentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledProjectEnvironmentOverview = styled('div', {
|
const StyledProjectEnvironmentOverview = styled('div')(({ theme }) => ({
|
||||||
shouldForwardProp: (prop) => prop !== 'enabled',
|
|
||||||
})<{ enabled: boolean }>(({ theme, enabled }) => ({
|
|
||||||
borderRadius: theme.shape.borderRadiusLarge,
|
|
||||||
marginBottom: theme.spacing(2),
|
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',
|
boxShadow: 'none',
|
||||||
background: 'none',
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
});
|
overflow: 'hidden',
|
||||||
|
|
||||||
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, {
|
const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
|
||||||
shouldForwardProp: (prop) => prop !== 'enabled',
|
padding: 0,
|
||||||
})<{ enabled: boolean }>(({ theme }) => ({
|
background: theme.palette.background.elevation1,
|
||||||
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 }) => ({
|
export const ProjectEnvironment = ({
|
||||||
width: '100%',
|
environment,
|
||||||
position: 'relative',
|
}: IProjectEnvironmentProps) => {
|
||||||
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 { environment: name } = environment;
|
||||||
const description = `Default strategy configuration in the ${name} environment`;
|
|
||||||
const theme = useTheme();
|
|
||||||
const enabled = false;
|
|
||||||
return (
|
return (
|
||||||
<StyledProjectEnvironmentOverview enabled={false}>
|
<StyledProjectEnvironmentOverview>
|
||||||
<StyledAccordion
|
<StyledAccordion
|
||||||
expanded={true}
|
expanded={true}
|
||||||
onChange={(e) => e.stopPropagation()}
|
onChange={(e) => e.stopPropagation()}
|
||||||
data-testid={`${PROJECT_ENVIRONMENT_ACCORDION}_${name}`}
|
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>
|
<EnvironmentHeader environmentId={name} expandable={false} />
|
||||||
<StyledHeader data-loading enabled={enabled}>
|
<StyledAccordionDetails>
|
||||||
<StyledHeaderTitle>
|
<ProjectEnvironmentDefaultStrategy
|
||||||
<StyledEnvironmentIcon enabled />
|
environment={environment}
|
||||||
<div>
|
/>
|
||||||
<StyledStringTruncator
|
|
||||||
text={name}
|
|
||||||
maxWidth='100'
|
|
||||||
maxLength={15}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</StyledHeaderTitle>
|
|
||||||
</StyledHeader>
|
|
||||||
</StyledAccordionSummary>
|
|
||||||
|
|
||||||
<StyledAccordionDetails enabled>
|
|
||||||
<StyledAccordionBody>
|
|
||||||
<StyledAccordionBodyInnerContainer>
|
|
||||||
<ProjectEnvironmentDefaultStrategy
|
|
||||||
environment={environment}
|
|
||||||
description={description}
|
|
||||||
/>
|
|
||||||
</StyledAccordionBodyInnerContainer>
|
|
||||||
</StyledAccordionBody>
|
|
||||||
</StyledAccordionDetails>
|
</StyledAccordionDetails>
|
||||||
</StyledAccordion>
|
</StyledAccordion>
|
||||||
</StyledProjectEnvironmentOverview>
|
</StyledProjectEnvironmentOverview>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProjectEnvironment;
|
|
||||||
|
@ -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;
|
@ -2,7 +2,6 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
|||||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Edit from '@mui/icons-material/Edit';
|
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 type { ProjectEnvironmentType } from 'interfaces/environments';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import type { CreateFeatureStrategySchema } from 'openapi';
|
import type { CreateFeatureStrategySchema } from 'openapi';
|
||||||
@ -10,12 +9,11 @@ import {
|
|||||||
PROJECT_DEFAULT_STRATEGY_WRITE,
|
PROJECT_DEFAULT_STRATEGY_WRITE,
|
||||||
UPDATE_PROJECT,
|
UPDATE_PROJECT,
|
||||||
} from '@server/types/permissions';
|
} from '@server/types/permissions';
|
||||||
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';
|
import { StrategyItem } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem';
|
||||||
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/LegacyStrategyItemContainer';
|
import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
|
|
||||||
interface ProjectEnvironmentDefaultStrategyProps {
|
interface ProjectEnvironmentDefaultStrategyProps {
|
||||||
environment: ProjectEnvironmentType;
|
environment: ProjectEnvironmentType;
|
||||||
description: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatEditProjectEnvironmentStrategyPath = (
|
export const formatEditProjectEnvironmentStrategyPath = (
|
||||||
@ -39,9 +37,8 @@ const DEFAULT_STRATEGY: CreateFeatureStrategySchema = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProjectEnvironmentDefaultStrategy = ({
|
export const ProjectEnvironmentDefaultStrategy = ({
|
||||||
environment,
|
environment,
|
||||||
description,
|
|
||||||
}: ProjectEnvironmentDefaultStrategyProps) => {
|
}: ProjectEnvironmentDefaultStrategyProps) => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const { environment: environmentId, defaultStrategy } = environment;
|
const { environment: environmentId, defaultStrategy } = environment;
|
||||||
@ -51,44 +48,42 @@ const ProjectEnvironmentDefaultStrategy = ({
|
|||||||
environmentId,
|
environmentId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const strategy: CreateFeatureStrategySchema = useMemo(() => {
|
const strategy: Omit<IFeatureStrategy, 'id'> = useMemo(() => {
|
||||||
return defaultStrategy ? defaultStrategy : DEFAULT_STRATEGY;
|
const baseDefaultStrategy = defaultStrategy
|
||||||
|
? defaultStrategy
|
||||||
|
: DEFAULT_STRATEGY;
|
||||||
|
return {
|
||||||
|
...baseDefaultStrategy,
|
||||||
|
disabled: false,
|
||||||
|
constraints: baseDefaultStrategy.constraints ?? [],
|
||||||
|
title: baseDefaultStrategy.title ?? '',
|
||||||
|
parameters: baseDefaultStrategy.parameters ?? {},
|
||||||
|
};
|
||||||
}, [JSON.stringify(defaultStrategy)]);
|
}, [JSON.stringify(defaultStrategy)]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<StrategyItem
|
||||||
<StrategyItemContainer
|
strategy={strategy}
|
||||||
strategy={strategy as any}
|
headerItemsRight={
|
||||||
description={description}
|
<>
|
||||||
actions={
|
<PermissionIconButton
|
||||||
<>
|
permission={[
|
||||||
<PermissionIconButton
|
PROJECT_DEFAULT_STRATEGY_WRITE,
|
||||||
permission={[
|
UPDATE_PROJECT,
|
||||||
PROJECT_DEFAULT_STRATEGY_WRITE,
|
]}
|
||||||
UPDATE_PROJECT,
|
environmentId={environmentId}
|
||||||
]}
|
projectId={projectId}
|
||||||
environmentId={environmentId}
|
component={Link}
|
||||||
projectId={projectId}
|
to={editStrategyPath}
|
||||||
component={Link}
|
tooltipProps={{
|
||||||
to={editStrategyPath}
|
title: `Edit default strategy for "${environmentId}"`,
|
||||||
tooltipProps={{
|
}}
|
||||||
title: `Edit default strategy for "${environmentId}"`,
|
data-testid={`STRATEGY_EDIT-${strategy.name}`}
|
||||||
}}
|
>
|
||||||
data-testid={`STRATEGY_EDIT-${strategy?.name}`}
|
<Edit />
|
||||||
>
|
</PermissionIconButton>
|
||||||
<Edit />
|
</>
|
||||||
</PermissionIconButton>
|
}
|
||||||
</>
|
/>
|
||||||
}
|
|
||||||
>
|
|
||||||
<StrategyExecution strategy={strategy} />
|
|
||||||
|
|
||||||
{strategy.variants && strategy.variants.length > 0 ? (
|
|
||||||
<SplitPreviewSlider variants={strategy.variants} />
|
|
||||||
) : null}
|
|
||||||
</StrategyItemContainer>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProjectEnvironmentDefaultStrategy;
|
|
||||||
|
Loading…
Reference in New Issue
Block a user