mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-10 17:53:36 +02:00
chore(1-3389): new env strategy containers (#9361)
Updates the strategy list based on the new designs and moves the current versions of the touched components into `Legacy...` files (the vast majority of changes are that and updating imports). The relevant changes to the components are listed in their original files. Flag on:  Flag off:  ## Next steps There's two items to review for improving these current comments (also noted inline): - Whether to aria-hide the "or" separator or not (I need to read up a bit and think whether it makes sense to show that or not) - Changing the list of strategies into an actual ordered list (`ol`). That'd reflect the semantics better. Next would be checking the other places we use strategy lists and then updating those too. In doing so, I might find that some things need to be updated, but I'll handle those when I get there. There's also handling release plans.
This commit is contained in:
parent
192bd83fa6
commit
e25fb9f7c0
@ -15,7 +15,7 @@ import { type IUseWeakMap, useWeakMap } from 'hooks/useWeakMap';
|
|||||||
import { objectId } from 'utils/objectId';
|
import { objectId } from 'utils/objectId';
|
||||||
import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
|
import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
|
|
||||||
export interface IConstraintAccordionListProps {
|
export interface IConstraintAccordionListProps {
|
||||||
constraints: IConstraint[];
|
constraints: IConstraint[];
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
createEmptyConstraint,
|
createEmptyConstraint,
|
||||||
} from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
|
} from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
import { NewConstraintAccordion } from 'component/common/NewConstraintAccordion/NewConstraintAccordion';
|
import { NewConstraintAccordion } from 'component/common/NewConstraintAccordion/NewConstraintAccordion';
|
||||||
|
|
||||||
export interface IConstraintAccordionListProps {
|
export interface IConstraintAccordionListProps {
|
||||||
|
@ -0,0 +1,205 @@
|
|||||||
|
// deprecated; remove with the `flagOverviewRedesign` flag
|
||||||
|
import type React from 'react';
|
||||||
|
import type { DragEventHandler, FC, ReactNode } from 'react';
|
||||||
|
import DragIndicator from '@mui/icons-material/DragIndicator';
|
||||||
|
import { Box, IconButton, styled } from '@mui/material';
|
||||||
|
import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
|
import {
|
||||||
|
formatStrategyName,
|
||||||
|
getFeatureStrategyIcon,
|
||||||
|
} from 'utils/strategyNames';
|
||||||
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import type { PlaygroundStrategySchema } from 'openapi';
|
||||||
|
import { Badge } from '../Badge/Badge';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
interface IStrategyItemContainerProps {
|
||||||
|
strategy: IFeatureStrategy | PlaygroundStrategySchema;
|
||||||
|
onDragStart?: DragEventHandler<HTMLButtonElement>;
|
||||||
|
onDragEnd?: DragEventHandler<HTMLButtonElement>;
|
||||||
|
actions?: ReactNode;
|
||||||
|
orderNumber?: number;
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
description?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DragIcon = styled(IconButton)({
|
||||||
|
padding: 0,
|
||||||
|
cursor: 'inherit',
|
||||||
|
transition: 'color 0.2s ease-in-out',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledIndexLabel = styled('div')(({ theme }) => ({
|
||||||
|
fontSize: theme.typography.fontSize,
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
position: 'absolute',
|
||||||
|
display: 'none',
|
||||||
|
right: 'calc(100% + 6px)',
|
||||||
|
top: theme.spacing(2.5),
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
display: 'block',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
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('div')({
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledContainer = styled(Box, {
|
||||||
|
shouldForwardProp: (prop) => prop !== 'disabled',
|
||||||
|
})<{ disabled?: boolean }>(({ theme, disabled }) => ({
|
||||||
|
borderRadius: theme.shape.borderRadiusMedium,
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
'& + &': {
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
},
|
||||||
|
background: disabled
|
||||||
|
? theme.palette.envAccordion.disabled
|
||||||
|
: theme.palette.background.paper,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledHeader = styled('div', {
|
||||||
|
shouldForwardProp: (prop) => prop !== 'draggable' && prop !== 'disabled',
|
||||||
|
})<{ draggable: boolean; disabled: boolean }>(
|
||||||
|
({ theme, draggable, disabled }) => ({
|
||||||
|
padding: theme.spacing(0.5, 2),
|
||||||
|
display: 'flex',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
alignItems: 'center',
|
||||||
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
|
paddingLeft: draggable ? theme.spacing(1) : theme.spacing(2),
|
||||||
|
color: disabled
|
||||||
|
? theme.palette.text.secondary
|
||||||
|
: theme.palette.text.primary,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const StrategyItemContainer: FC<IStrategyItemContainerProps> = ({
|
||||||
|
strategy,
|
||||||
|
onDragStart,
|
||||||
|
onDragEnd,
|
||||||
|
actions,
|
||||||
|
children,
|
||||||
|
orderNumber,
|
||||||
|
style = {},
|
||||||
|
description,
|
||||||
|
}) => {
|
||||||
|
const Icon = getFeatureStrategyIcon(strategy.name);
|
||||||
|
|
||||||
|
const StrategyHeaderLink: React.FC<{ children?: React.ReactNode }> =
|
||||||
|
'links' in strategy
|
||||||
|
? ({ children }) => <Link to={strategy.links.edit}>{children}</Link>
|
||||||
|
: ({ children }) => <> {children} </>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ position: 'relative' }}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={orderNumber !== undefined}
|
||||||
|
show={<StyledIndexLabel>{orderNumber}</StyledIndexLabel>}
|
||||||
|
/>
|
||||||
|
<StyledContainer
|
||||||
|
disabled={strategy?.disabled || false}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
<StyledHeader
|
||||||
|
draggable={Boolean(onDragStart)}
|
||||||
|
disabled={Boolean(strategy?.disabled)}
|
||||||
|
>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(onDragStart)}
|
||||||
|
show={() => (
|
||||||
|
<DragIcon
|
||||||
|
draggable
|
||||||
|
disableRipple
|
||||||
|
size='small'
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
sx={{ cursor: 'move' }}
|
||||||
|
>
|
||||||
|
<DragIndicator
|
||||||
|
titleAccess='Drag to reorder'
|
||||||
|
cursor='grab'
|
||||||
|
sx={{ color: 'action.active' }}
|
||||||
|
/>
|
||||||
|
</DragIcon>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
sx={{
|
||||||
|
fill: (theme) => theme.palette.action.disabled,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<StyledHeaderContainer>
|
||||||
|
<StrategyHeaderLink>
|
||||||
|
<StringTruncator
|
||||||
|
maxWidth='400'
|
||||||
|
maxLength={15}
|
||||||
|
text={formatStrategyName(String(strategy.name))}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(strategy.title)}
|
||||||
|
show={
|
||||||
|
<StyledCustomTitle>
|
||||||
|
{formatStrategyName(
|
||||||
|
String(strategy.title),
|
||||||
|
)}
|
||||||
|
</StyledCustomTitle>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StrategyHeaderLink>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(description)}
|
||||||
|
show={
|
||||||
|
<StyledDescription>
|
||||||
|
{description}
|
||||||
|
</StyledDescription>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledHeaderContainer>
|
||||||
|
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(strategy?.disabled)}
|
||||||
|
show={() => (
|
||||||
|
<>
|
||||||
|
<Badge color='disabled'>Disabled</Badge>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
marginLeft: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
minHeight: (theme) => theme.spacing(6),
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{actions}
|
||||||
|
</Box>
|
||||||
|
</StyledHeader>
|
||||||
|
<Box sx={{ p: 2 }}>{children}</Box>
|
||||||
|
</StyledContainer>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { render } from 'utils/testRenderer';
|
import { render } from 'utils/testRenderer';
|
||||||
import { StrategyItemContainer } from './StrategyItemContainer';
|
import { StrategyItemContainer } from './LegacyStrategyItemContainer';
|
||||||
import type { IFeatureStrategy } from 'interfaces/strategy';
|
import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
|
|
||||||
test('should render strategy name, custom title and description', async () => {
|
test('should render strategy name, custom title and description', async () => {
|
||||||
|
@ -31,17 +31,6 @@ const DragIcon = styled(IconButton)({
|
|||||||
transition: 'color 0.2s ease-in-out',
|
transition: 'color 0.2s ease-in-out',
|
||||||
});
|
});
|
||||||
|
|
||||||
const StyledIndexLabel = styled('div')(({ theme }) => ({
|
|
||||||
fontSize: theme.typography.fontSize,
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
position: 'absolute',
|
|
||||||
display: 'none',
|
|
||||||
right: 'calc(100% + 6px)',
|
|
||||||
top: theme.spacing(2.5),
|
|
||||||
[theme.breakpoints.up('md')]: {
|
|
||||||
display: 'block',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
const StyledDescription = styled('div')(({ theme }) => ({
|
const StyledDescription = styled('div')(({ theme }) => ({
|
||||||
fontSize: theme.typography.fontSize,
|
fontSize: theme.typography.fontSize,
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
@ -65,20 +54,13 @@ const StyledHeaderContainer = styled('div')({
|
|||||||
verticalAlign: 'middle',
|
verticalAlign: 'middle',
|
||||||
});
|
});
|
||||||
|
|
||||||
const StyledContainer = styled(Box, {
|
const NewStyledContainer = styled(Box, {
|
||||||
shouldForwardProp: (prop) => prop !== 'disabled',
|
shouldForwardProp: (prop) => prop !== 'disabled',
|
||||||
})<{ disabled?: boolean }>(({ theme, disabled }) => ({
|
})({
|
||||||
borderRadius: theme.shape.borderRadiusMedium,
|
background: 'inherit',
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
});
|
||||||
'& + &': {
|
|
||||||
marginTop: theme.spacing(2),
|
|
||||||
},
|
|
||||||
background: disabled
|
|
||||||
? theme.palette.envAccordion.disabled
|
|
||||||
: theme.palette.background.paper,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledHeader = styled('div', {
|
const NewStyledHeader = 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 }) => ({
|
||||||
@ -86,7 +68,6 @@ const StyledHeader = styled('div', {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
||||||
fontWeight: theme.typography.fontWeightMedium,
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
paddingLeft: draggable ? theme.spacing(1) : theme.spacing(2),
|
paddingLeft: draggable ? theme.spacing(1) : theme.spacing(2),
|
||||||
color: disabled
|
color: disabled
|
||||||
@ -101,7 +82,6 @@ export const StrategyItemContainer: FC<IStrategyItemContainerProps> = ({
|
|||||||
onDragEnd,
|
onDragEnd,
|
||||||
actions,
|
actions,
|
||||||
children,
|
children,
|
||||||
orderNumber,
|
|
||||||
style = {},
|
style = {},
|
||||||
description,
|
description,
|
||||||
}) => {
|
}) => {
|
||||||
@ -114,15 +94,8 @@ export const StrategyItemContainer: FC<IStrategyItemContainerProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ position: 'relative' }}>
|
<Box sx={{ position: 'relative' }}>
|
||||||
<ConditionallyRender
|
<NewStyledContainer style={style}>
|
||||||
condition={orderNumber !== undefined}
|
<NewStyledHeader
|
||||||
show={<StyledIndexLabel>{orderNumber}</StyledIndexLabel>}
|
|
||||||
/>
|
|
||||||
<StyledContainer
|
|
||||||
disabled={strategy?.disabled || false}
|
|
||||||
style={style}
|
|
||||||
>
|
|
||||||
<StyledHeader
|
|
||||||
draggable={Boolean(onDragStart)}
|
draggable={Boolean(onDragStart)}
|
||||||
disabled={Boolean(strategy?.disabled)}
|
disabled={Boolean(strategy?.disabled)}
|
||||||
>
|
>
|
||||||
@ -196,9 +169,9 @@ export const StrategyItemContainer: FC<IStrategyItemContainerProps> = ({
|
|||||||
>
|
>
|
||||||
{actions}
|
{actions}
|
||||||
</Box>
|
</Box>
|
||||||
</StyledHeader>
|
</NewStyledHeader>
|
||||||
<Box sx={{ p: 2 }}>{children}</Box>
|
<Box sx={{ p: 2, pt: 0 }}>{children}</Box>
|
||||||
</StyledContainer>
|
</NewStyledContainer>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
// deprecated; remove with the `flagOverviewRedesign` flag
|
||||||
|
import { Box, styled, useTheme } from '@mui/material';
|
||||||
|
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
|
interface IStrategySeparatorProps {
|
||||||
|
text: 'AND' | 'OR';
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledContent = styled('div')(({ theme }) => ({
|
||||||
|
padding: theme.spacing(0.75, 1),
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
|
backgroundColor: theme.palette.background.elevation2,
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: theme.zIndex.fab,
|
||||||
|
top: '50%',
|
||||||
|
left: theme.spacing(2),
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
lineHeight: 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledCenteredContent = styled(StyledContent)(({ theme }) => ({
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
backgroundColor: theme.palette.seen.primary,
|
||||||
|
borderRadius: theme.shape.borderRadiusLarge,
|
||||||
|
padding: theme.spacing(0.75, 1.5),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const StrategySeparator = ({ text }: IStrategySeparatorProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: theme.spacing(text === 'AND' ? 1 : 1.5),
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={text === 'AND'}
|
||||||
|
show={() => <StyledContent>{text}</StyledContent>}
|
||||||
|
elseShow={() => (
|
||||||
|
<StyledCenteredContent>{text}</StyledCenteredContent>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -1,51 +1,57 @@
|
|||||||
import { Box, styled, useTheme } from '@mui/material';
|
import { Box, styled, useTheme } from '@mui/material';
|
||||||
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
|
|
||||||
|
|
||||||
interface IStrategySeparatorProps {
|
interface IStrategySeparatorProps {
|
||||||
text: 'AND' | 'OR';
|
text: 'AND' | 'OR';
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledContent = styled('div')(({ theme }) => ({
|
const StyledAnd = styled('div')(({ theme }) => ({
|
||||||
padding: theme.spacing(0.75, 1),
|
padding: theme.spacing(0.75, 1),
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
fontSize: theme.fontSizes.smallerBody,
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
backgroundColor: theme.palette.background.elevation2,
|
backgroundColor: theme.palette.background.elevation2,
|
||||||
borderRadius: theme.shape.borderRadius,
|
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
zIndex: theme.zIndex.fab,
|
zIndex: theme.zIndex.fab,
|
||||||
top: '50%',
|
top: '50%',
|
||||||
left: theme.spacing(2),
|
left: theme.spacing(2),
|
||||||
transform: 'translateY(-50%)',
|
transform: 'translateY(-50%)',
|
||||||
lineHeight: 1,
|
lineHeight: 1,
|
||||||
|
borderRadius: theme.shape.borderRadiusLarge,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledCenteredContent = styled(StyledContent)(({ theme }) => ({
|
const StyledOr = styled(StyledAnd)(({ theme }) => ({
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor: theme.palette.background.alternative,
|
||||||
|
color: theme.palette.primary.contrastText,
|
||||||
|
left: theme.spacing(4),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledSeparator = styled('hr')(({ theme }) => ({
|
||||||
|
border: 0,
|
||||||
|
borderTop: `1px solid ${theme.palette.divider}`,
|
||||||
|
margin: 0,
|
||||||
|
position: 'absolute',
|
||||||
top: '50%',
|
top: '50%',
|
||||||
left: '50%',
|
width: '100%',
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
backgroundColor: theme.palette.seen.primary,
|
|
||||||
borderRadius: theme.shape.borderRadiusLarge,
|
|
||||||
padding: theme.spacing(0.75, 1.5),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const StrategySeparator = ({ text }: IStrategySeparatorProps) => {
|
export const StrategySeparator = ({ text }: IStrategySeparatorProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
height: theme.spacing(text === 'AND' ? 1 : 1.5),
|
height: theme.spacing(text === 'AND' ? 1 : 1.5),
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: '100%',
|
|
||||||
}}
|
}}
|
||||||
|
aria-hidden={true}
|
||||||
>
|
>
|
||||||
<ConditionallyRender
|
{text === 'AND' ? (
|
||||||
condition={text === 'AND'}
|
<StyledAnd>{text}</StyledAnd>
|
||||||
show={() => <StyledContent>{text}</StyledContent>}
|
) : (
|
||||||
elseShow={() => (
|
<>
|
||||||
<StyledCenteredContent>{text}</StyledCenteredContent>
|
<StyledSeparator />
|
||||||
)}
|
<StyledOr>{text}</StyledOr>
|
||||||
/>
|
</>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -9,7 +9,7 @@ import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFe
|
|||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { StrategyDraggableItem } from './StrategyDraggableItem/StrategyDraggableItem';
|
import { StrategyDraggableItem } from './StrategyDraggableItem/LegacyStrategyDraggableItem';
|
||||||
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
|
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||||
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
|
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
@ -25,6 +25,7 @@ import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePla
|
|||||||
import { ReleasePlan } from '../../../ReleasePlan/ReleasePlan';
|
import { ReleasePlan } from '../../../ReleasePlan/ReleasePlan';
|
||||||
import { Badge } from 'component/common/Badge/Badge';
|
import { Badge } from 'component/common/Badge/Badge';
|
||||||
import { SectionSeparator } from '../SectionSeparator/SectionSeparator';
|
import { SectionSeparator } from '../SectionSeparator/SectionSeparator';
|
||||||
|
import { StrategyDraggableItem as NewStrategyDraggableItem } from './StrategyDraggableItem/StrategyDraggableItem';
|
||||||
|
|
||||||
interface IEnvironmentAccordionBodyProps {
|
interface IEnvironmentAccordionBodyProps {
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
@ -59,7 +60,7 @@ const AdditionalStrategiesDiv = styled('div')(({ theme }) => ({
|
|||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const EnvironmentAccordionBody = ({
|
export const EnvironmentAccordionBody = ({
|
||||||
featureEnvironment,
|
featureEnvironment,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
otherEnvironments,
|
otherEnvironments,
|
||||||
@ -223,18 +224,6 @@ const EnvironmentAccordionBody = ({
|
|||||||
return (
|
return (
|
||||||
<StyledAccordionBody>
|
<StyledAccordionBody>
|
||||||
<StyledAccordionBodyInnerContainer>
|
<StyledAccordionBodyInnerContainer>
|
||||||
<ConditionallyRender
|
|
||||||
condition={
|
|
||||||
(releasePlans.length > 0 || strategies.length > 0) &&
|
|
||||||
isDisabled
|
|
||||||
}
|
|
||||||
show={() => (
|
|
||||||
<Alert severity='warning' sx={{ mb: 2 }}>
|
|
||||||
This environment is disabled, which means that none
|
|
||||||
of your strategies are executing.
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={releasePlans.length > 0 || strategies.length > 0}
|
condition={releasePlans.length > 0 || strategies.length > 0}
|
||||||
show={
|
show={
|
||||||
@ -270,7 +259,7 @@ const EnvironmentAccordionBody = ({
|
|||||||
show={
|
show={
|
||||||
<>
|
<>
|
||||||
{strategies.map((strategy, index) => (
|
{strategies.map((strategy, index) => (
|
||||||
<StrategyDraggableItem
|
<NewStrategyDraggableItem
|
||||||
key={strategy.id}
|
key={strategy.id}
|
||||||
strategy={strategy}
|
strategy={strategy}
|
||||||
index={index}
|
index={index}
|
||||||
@ -349,5 +338,3 @@ const EnvironmentAccordionBody = ({
|
|||||||
</StyledAccordionBody>
|
</StyledAccordionBody>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EnvironmentAccordionBody;
|
|
||||||
|
@ -0,0 +1,354 @@
|
|||||||
|
// deprecated; remove with the `flagOverviewRedesign` flag
|
||||||
|
import {
|
||||||
|
type DragEventHandler,
|
||||||
|
type RefObject,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { Alert, Pagination, styled } from '@mui/material';
|
||||||
|
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
|
||||||
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { StrategyDraggableItem } from './StrategyDraggableItem/LegacyStrategyDraggableItem';
|
||||||
|
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||||
|
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
|
||||||
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
|
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||||
|
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||||
|
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
|
||||||
|
import usePagination from 'hooks/usePagination';
|
||||||
|
import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
|
||||||
|
import { ReleasePlan } from '../../../ReleasePlan/ReleasePlan';
|
||||||
|
import { Badge } from 'component/common/Badge/Badge';
|
||||||
|
import { SectionSeparator } from '../SectionSeparator/SectionSeparator';
|
||||||
|
|
||||||
|
interface IEnvironmentAccordionBodyProps {
|
||||||
|
isDisabled: boolean;
|
||||||
|
featureEnvironment?: IFeatureEnvironment;
|
||||||
|
otherEnvironments?: IFeatureEnvironment['name'][];
|
||||||
|
}
|
||||||
|
|
||||||
|
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 StyledBadge = styled(Badge)(({ theme }) => ({
|
||||||
|
backgroundColor: theme.palette.primary.light,
|
||||||
|
border: 'none',
|
||||||
|
padding: theme.spacing(0.75, 1.5),
|
||||||
|
borderRadius: theme.shape.borderRadiusLarge,
|
||||||
|
color: theme.palette.common.white,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const AdditionalStrategiesDiv = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const EnvironmentAccordionBody = ({
|
||||||
|
featureEnvironment,
|
||||||
|
isDisabled,
|
||||||
|
otherEnvironments,
|
||||||
|
}: IEnvironmentAccordionBodyProps) => {
|
||||||
|
const projectId = useRequiredPathParam('projectId');
|
||||||
|
const featureId = useRequiredPathParam('featureId');
|
||||||
|
const { setStrategiesSortOrder } = useFeatureStrategyApi();
|
||||||
|
const { addChange } = useChangeRequestApi();
|
||||||
|
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
|
||||||
|
const { refetch: refetchChangeRequests } =
|
||||||
|
usePendingChangeRequests(projectId);
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
const { refetchFeature } = useFeature(projectId, featureId);
|
||||||
|
const manyStrategiesPagination = useUiFlag('manyStrategiesPagination');
|
||||||
|
const [strategies, setStrategies] = useState(
|
||||||
|
featureEnvironment?.strategies || [],
|
||||||
|
);
|
||||||
|
const { releasePlans } = useReleasePlans(
|
||||||
|
projectId,
|
||||||
|
featureId,
|
||||||
|
featureEnvironment?.name,
|
||||||
|
);
|
||||||
|
const { trackEvent } = usePlausibleTracker();
|
||||||
|
|
||||||
|
const [dragItem, setDragItem] = useState<{
|
||||||
|
id: string;
|
||||||
|
index: number;
|
||||||
|
height: number;
|
||||||
|
} | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
// Use state to enable drag and drop, but switch to API output when it arrives
|
||||||
|
setStrategies(featureEnvironment?.strategies || []);
|
||||||
|
}, [featureEnvironment?.strategies]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (strategies.length > 50) {
|
||||||
|
trackEvent('many-strategies');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!featureEnvironment) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageSize = 20;
|
||||||
|
const { page, pages, setPageIndex, pageIndex } =
|
||||||
|
usePagination<IFeatureStrategy>(strategies, pageSize);
|
||||||
|
|
||||||
|
const onReorder = async (payload: { id: string; sortOrder: number }[]) => {
|
||||||
|
try {
|
||||||
|
await setStrategiesSortOrder(
|
||||||
|
projectId,
|
||||||
|
featureId,
|
||||||
|
featureEnvironment.name,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
refetchFeature();
|
||||||
|
setToastData({
|
||||||
|
text: 'Order of strategies updated',
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeRequestReorder = async (
|
||||||
|
payload: { id: string; sortOrder: number }[],
|
||||||
|
) => {
|
||||||
|
await addChange(projectId, featureEnvironment.name, {
|
||||||
|
action: 'reorderStrategy',
|
||||||
|
feature: featureId,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
setToastData({
|
||||||
|
text: 'Strategy execution order added to draft',
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
refetchChangeRequests();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onStrategyReorder = async (
|
||||||
|
payload: { id: string; sortOrder: number }[],
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
if (isChangeRequestConfigured(featureEnvironment.name)) {
|
||||||
|
await onChangeRequestReorder(payload);
|
||||||
|
} else {
|
||||||
|
await onReorder(payload);
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragStartRef =
|
||||||
|
(
|
||||||
|
ref: RefObject<HTMLDivElement>,
|
||||||
|
index: number,
|
||||||
|
): DragEventHandler<HTMLButtonElement> =>
|
||||||
|
(event) => {
|
||||||
|
setDragItem({
|
||||||
|
id: strategies[index].id,
|
||||||
|
index,
|
||||||
|
height: ref.current?.offsetHeight || 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ref?.current) {
|
||||||
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
|
event.dataTransfer.setData('text/html', ref.current.outerHTML);
|
||||||
|
event.dataTransfer.setDragImage(ref.current, 20, 20);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragOver =
|
||||||
|
(targetId: string) =>
|
||||||
|
(
|
||||||
|
ref: RefObject<HTMLDivElement>,
|
||||||
|
targetIndex: number,
|
||||||
|
): DragEventHandler<HTMLDivElement> =>
|
||||||
|
(event) => {
|
||||||
|
if (dragItem === null || ref.current === null) return;
|
||||||
|
if (dragItem.index === targetIndex || targetId === dragItem.id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const { top, bottom } = ref.current.getBoundingClientRect();
|
||||||
|
const overTargetTop = event.clientY - top < dragItem.height;
|
||||||
|
const overTargetBottom = bottom - event.clientY < dragItem.height;
|
||||||
|
const draggingUp = dragItem.index > targetIndex;
|
||||||
|
|
||||||
|
// prevent oscillating by only reordering if there is sufficient space
|
||||||
|
if (
|
||||||
|
(overTargetTop && draggingUp) ||
|
||||||
|
(overTargetBottom && !draggingUp)
|
||||||
|
) {
|
||||||
|
const newStrategies = [...strategies];
|
||||||
|
const movedStrategy = newStrategies.splice(
|
||||||
|
dragItem.index,
|
||||||
|
1,
|
||||||
|
)[0];
|
||||||
|
newStrategies.splice(targetIndex, 0, movedStrategy);
|
||||||
|
setStrategies(newStrategies);
|
||||||
|
setDragItem({
|
||||||
|
...dragItem,
|
||||||
|
index: targetIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragEnd = () => {
|
||||||
|
setDragItem(null);
|
||||||
|
onStrategyReorder(
|
||||||
|
strategies.map((strategy, sortOrder) => ({
|
||||||
|
id: strategy.id,
|
||||||
|
sortOrder,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledAccordionBody>
|
||||||
|
<StyledAccordionBodyInnerContainer>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={
|
||||||
|
(releasePlans.length > 0 || strategies.length > 0) &&
|
||||||
|
isDisabled
|
||||||
|
}
|
||||||
|
show={() => (
|
||||||
|
<Alert severity='warning' sx={{ mb: 2 }}>
|
||||||
|
This environment is disabled, which means that none
|
||||||
|
of your strategies are executing.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={releasePlans.length > 0 || strategies.length > 0}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
{releasePlans.map((plan) => (
|
||||||
|
<ReleasePlan
|
||||||
|
key={plan.id}
|
||||||
|
plan={plan}
|
||||||
|
environmentIsDisabled={isDisabled}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={
|
||||||
|
releasePlans.length > 0 &&
|
||||||
|
strategies.length > 0
|
||||||
|
}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
<SectionSeparator>
|
||||||
|
<StyledBadge>OR</StyledBadge>
|
||||||
|
</SectionSeparator>
|
||||||
|
<AdditionalStrategiesDiv>
|
||||||
|
Additional strategies
|
||||||
|
</AdditionalStrategiesDiv>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={
|
||||||
|
strategies.length < 50 ||
|
||||||
|
!manyStrategiesPagination
|
||||||
|
}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
{strategies.map((strategy, index) => (
|
||||||
|
<StrategyDraggableItem
|
||||||
|
key={strategy.id}
|
||||||
|
strategy={strategy}
|
||||||
|
index={index}
|
||||||
|
environmentName={
|
||||||
|
featureEnvironment.name
|
||||||
|
}
|
||||||
|
otherEnvironments={
|
||||||
|
otherEnvironments
|
||||||
|
}
|
||||||
|
isDragging={
|
||||||
|
dragItem?.id === strategy.id
|
||||||
|
}
|
||||||
|
onDragStartRef={onDragStartRef}
|
||||||
|
onDragOver={onDragOver(
|
||||||
|
strategy.id,
|
||||||
|
)}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<>
|
||||||
|
<Alert severity='error'>
|
||||||
|
We noticed you're using a high
|
||||||
|
number of activation strategies. To
|
||||||
|
ensure a more targeted approach,
|
||||||
|
consider leveraging constraints or
|
||||||
|
segments.
|
||||||
|
</Alert>
|
||||||
|
<br />
|
||||||
|
{page.map((strategy, index) => (
|
||||||
|
<StrategyDraggableItem
|
||||||
|
key={strategy.id}
|
||||||
|
strategy={strategy}
|
||||||
|
index={
|
||||||
|
index + pageIndex * pageSize
|
||||||
|
}
|
||||||
|
environmentName={
|
||||||
|
featureEnvironment.name
|
||||||
|
}
|
||||||
|
otherEnvironments={
|
||||||
|
otherEnvironments
|
||||||
|
}
|
||||||
|
isDragging={false}
|
||||||
|
onDragStartRef={
|
||||||
|
(() => {}) as any
|
||||||
|
}
|
||||||
|
onDragOver={(() => {}) as any}
|
||||||
|
onDragEnd={(() => {}) as any}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<br />
|
||||||
|
<Pagination
|
||||||
|
count={pages.length}
|
||||||
|
shape='rounded'
|
||||||
|
page={pageIndex + 1}
|
||||||
|
onChange={(_, page) =>
|
||||||
|
setPageIndex(page - 1)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<FeatureStrategyEmpty
|
||||||
|
projectId={projectId}
|
||||||
|
featureId={featureId}
|
||||||
|
environmentId={featureEnvironment.name}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledAccordionBodyInnerContainer>
|
||||||
|
</StyledAccordionBody>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnvironmentAccordionBody;
|
@ -0,0 +1,150 @@
|
|||||||
|
// deprecated; remove with the `flagOverviewRedesign` flag
|
||||||
|
import { type DragEventHandler, type RefObject, useRef } from 'react';
|
||||||
|
import { Box, useMediaQuery, useTheme } from '@mui/material';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
|
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||||
|
import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
|
import { StrategyItem } from './StrategyItem/LegacyStrategyItem';
|
||||||
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
import {
|
||||||
|
useStrategyChangesFromRequest,
|
||||||
|
type UseStrategyChangeFromRequestResult,
|
||||||
|
} from './StrategyItem/useStrategyChangesFromRequest';
|
||||||
|
import { ChangesScheduledBadge } from 'component/changeRequest/ModifiedInChangeRequestStatusBadge/ChangesScheduledBadge';
|
||||||
|
import type { IFeatureChange } from 'component/changeRequest/changeRequest.types';
|
||||||
|
import { Badge } from 'component/common/Badge/Badge';
|
||||||
|
import {
|
||||||
|
type ScheduledChangeRequestViewModel,
|
||||||
|
useScheduledChangeRequestsWithStrategy,
|
||||||
|
} from 'hooks/api/getters/useScheduledChangeRequestsWithStrategy/useScheduledChangeRequestsWithStrategy';
|
||||||
|
|
||||||
|
interface IStrategyDraggableItemProps {
|
||||||
|
strategy: IFeatureStrategy;
|
||||||
|
environmentName: string;
|
||||||
|
index: number;
|
||||||
|
otherEnvironments?: IFeatureEnvironment['name'][];
|
||||||
|
isDragging?: boolean;
|
||||||
|
onDragStartRef: (
|
||||||
|
ref: RefObject<HTMLDivElement>,
|
||||||
|
index: number,
|
||||||
|
) => DragEventHandler<HTMLButtonElement>;
|
||||||
|
onDragOver: (
|
||||||
|
ref: RefObject<HTMLDivElement>,
|
||||||
|
index: number,
|
||||||
|
) => DragEventHandler<HTMLDivElement>;
|
||||||
|
onDragEnd: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StrategyDraggableItem = ({
|
||||||
|
strategy,
|
||||||
|
index,
|
||||||
|
environmentName,
|
||||||
|
otherEnvironments,
|
||||||
|
isDragging,
|
||||||
|
onDragStartRef,
|
||||||
|
onDragOver,
|
||||||
|
onDragEnd,
|
||||||
|
}: IStrategyDraggableItemProps) => {
|
||||||
|
const projectId = useRequiredPathParam('projectId');
|
||||||
|
const featureId = useRequiredPathParam('featureId');
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const strategyChangesFromRequest = useStrategyChangesFromRequest(
|
||||||
|
projectId,
|
||||||
|
featureId,
|
||||||
|
environmentName,
|
||||||
|
strategy.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { changeRequests: scheduledChangesUsingStrategy } =
|
||||||
|
useScheduledChangeRequestsWithStrategy(projectId, strategy.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={strategy.id}
|
||||||
|
ref={ref}
|
||||||
|
onDragOver={onDragOver(ref, index)}
|
||||||
|
sx={{ opacity: isDragging ? '0.5' : '1' }}
|
||||||
|
>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={index > 0}
|
||||||
|
show={<StrategySeparator text='OR' />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StrategyItem
|
||||||
|
strategy={strategy}
|
||||||
|
environmentId={environmentName}
|
||||||
|
otherEnvironments={otherEnvironments}
|
||||||
|
onDragStart={onDragStartRef(ref, index)}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
orderNumber={index + 1}
|
||||||
|
headerChildren={renderHeaderChildren(
|
||||||
|
strategyChangesFromRequest,
|
||||||
|
scheduledChangesUsingStrategy,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ChangeRequestStatusBadge = ({
|
||||||
|
change,
|
||||||
|
}: {
|
||||||
|
change: IFeatureChange | undefined;
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
|
if (isSmallScreen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ mr: 1.5 }}>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={change?.action === 'updateStrategy'}
|
||||||
|
show={<Badge color='warning'>Modified in draft</Badge>}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={change?.action === 'deleteStrategy'}
|
||||||
|
show={<Badge color='error'>Deleted in draft</Badge>}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderHeaderChildren = (
|
||||||
|
changes?: UseStrategyChangeFromRequestResult,
|
||||||
|
scheduledChanges?: ScheduledChangeRequestViewModel[],
|
||||||
|
): JSX.Element[] => {
|
||||||
|
const badges: JSX.Element[] = [];
|
||||||
|
if (changes?.length === 0 && scheduledChanges?.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const draftChange = changes?.find(
|
||||||
|
({ isScheduledChange }) => !isScheduledChange,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (draftChange) {
|
||||||
|
badges.push(
|
||||||
|
<ChangeRequestStatusBadge
|
||||||
|
key={`draft-change#${draftChange.change.id}`}
|
||||||
|
change={draftChange.change}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scheduledChanges && scheduledChanges.length > 0) {
|
||||||
|
badges.push(
|
||||||
|
<ChangesScheduledBadge
|
||||||
|
key='scheduled-changes'
|
||||||
|
scheduledChangeRequestIds={scheduledChanges.map(
|
||||||
|
(scheduledChange) => scheduledChange.id,
|
||||||
|
)}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return badges;
|
||||||
|
};
|
@ -1,10 +1,8 @@
|
|||||||
import { type DragEventHandler, type RefObject, useRef } from 'react';
|
import { type DragEventHandler, type RefObject, useRef } from 'react';
|
||||||
import { Box, useMediaQuery, useTheme } from '@mui/material';
|
import { Box, useMediaQuery, useTheme } from '@mui/material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
|
||||||
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
|
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||||
import type { IFeatureStrategy } from 'interfaces/strategy';
|
import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
import { StrategyItem } from './StrategyItem/StrategyItem';
|
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import {
|
import {
|
||||||
useStrategyChangesFromRequest,
|
useStrategyChangesFromRequest,
|
||||||
@ -17,6 +15,8 @@ import {
|
|||||||
type ScheduledChangeRequestViewModel,
|
type ScheduledChangeRequestViewModel,
|
||||||
useScheduledChangeRequestsWithStrategy,
|
useScheduledChangeRequestsWithStrategy,
|
||||||
} from 'hooks/api/getters/useScheduledChangeRequestsWithStrategy/useScheduledChangeRequestsWithStrategy';
|
} from 'hooks/api/getters/useScheduledChangeRequestsWithStrategy/useScheduledChangeRequestsWithStrategy';
|
||||||
|
import { StrategySeparator as NewStrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
||||||
|
import { StrategyItem as NewStrategyItem } from './StrategyItem/StrategyItem';
|
||||||
|
|
||||||
interface IStrategyDraggableItemProps {
|
interface IStrategyDraggableItemProps {
|
||||||
strategy: IFeatureStrategy;
|
strategy: IFeatureStrategy;
|
||||||
@ -67,10 +67,10 @@ export const StrategyDraggableItem = ({
|
|||||||
>
|
>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={index > 0}
|
condition={index > 0}
|
||||||
show={<StrategySeparator text='OR' />}
|
show={<NewStrategySeparator text='OR' />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StrategyItem
|
<NewStrategyItem
|
||||||
strategy={strategy}
|
strategy={strategy}
|
||||||
environmentId={environmentName}
|
environmentId={environmentName}
|
||||||
otherEnvironments={otherEnvironments}
|
otherEnvironments={otherEnvironments}
|
||||||
|
@ -0,0 +1,181 @@
|
|||||||
|
// deprecated; remove with the `flagOverviewRedesign` flag
|
||||||
|
import type { DragEventHandler, FC } from 'react';
|
||||||
|
import Edit from '@mui/icons-material/Edit';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||||
|
import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
|
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||||
|
import { UPDATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { formatEditStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
|
||||||
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
import { StrategyExecution } from './StrategyExecution/StrategyExecution';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMenu';
|
||||||
|
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/LegacyStrategyItemContainer';
|
||||||
|
import MenuStrategyRemove from './MenuStrategyRemove/MenuStrategyRemove';
|
||||||
|
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { StrategyItemContainer as NewStrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
|
||||||
|
interface IStrategyItemProps {
|
||||||
|
environmentId: string;
|
||||||
|
strategy: IFeatureStrategy;
|
||||||
|
onDragStart?: DragEventHandler<HTMLButtonElement>;
|
||||||
|
onDragEnd?: DragEventHandler<HTMLButtonElement>;
|
||||||
|
otherEnvironments?: IFeatureEnvironment['name'][];
|
||||||
|
orderNumber?: number;
|
||||||
|
headerChildren?: JSX.Element[] | JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StrategyItem: FC<IStrategyItemProps> = ({
|
||||||
|
environmentId,
|
||||||
|
strategy,
|
||||||
|
onDragStart,
|
||||||
|
onDragEnd,
|
||||||
|
otherEnvironments,
|
||||||
|
orderNumber,
|
||||||
|
headerChildren,
|
||||||
|
}) => {
|
||||||
|
const projectId = useRequiredPathParam('projectId');
|
||||||
|
const featureId = useRequiredPathParam('featureId');
|
||||||
|
|
||||||
|
const editStrategyPath = formatEditStrategyPath(
|
||||||
|
projectId,
|
||||||
|
featureId,
|
||||||
|
environmentId,
|
||||||
|
strategy.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StrategyItemContainer
|
||||||
|
strategy={strategy}
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
orderNumber={orderNumber}
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
{headerChildren}
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(
|
||||||
|
otherEnvironments && otherEnvironments?.length > 0,
|
||||||
|
)}
|
||||||
|
show={() => (
|
||||||
|
<CopyStrategyIconMenu
|
||||||
|
environmentId={environmentId}
|
||||||
|
environments={otherEnvironments as string[]}
|
||||||
|
strategy={strategy}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<PermissionIconButton
|
||||||
|
permission={UPDATE_FEATURE_STRATEGY}
|
||||||
|
environmentId={environmentId}
|
||||||
|
projectId={projectId}
|
||||||
|
component={Link}
|
||||||
|
to={editStrategyPath}
|
||||||
|
tooltipProps={{
|
||||||
|
title: 'Edit strategy',
|
||||||
|
}}
|
||||||
|
data-testid={`STRATEGY_EDIT-${strategy.name}`}
|
||||||
|
>
|
||||||
|
<Edit />
|
||||||
|
</PermissionIconButton>
|
||||||
|
<MenuStrategyRemove
|
||||||
|
projectId={projectId}
|
||||||
|
featureId={featureId}
|
||||||
|
environmentId={environmentId}
|
||||||
|
strategy={strategy}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StrategyExecution strategy={strategy} />
|
||||||
|
|
||||||
|
{strategy.variants &&
|
||||||
|
strategy.variants.length > 0 &&
|
||||||
|
(strategy.disabled ? (
|
||||||
|
<Box sx={{ opacity: '0.5' }}>
|
||||||
|
<SplitPreviewSlider variants={strategy.variants} />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<SplitPreviewSlider variants={strategy.variants} />
|
||||||
|
))}
|
||||||
|
</StrategyItemContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NewStrategyItem: FC<IStrategyItemProps> = ({
|
||||||
|
environmentId,
|
||||||
|
strategy,
|
||||||
|
onDragStart,
|
||||||
|
onDragEnd,
|
||||||
|
otherEnvironments,
|
||||||
|
orderNumber,
|
||||||
|
headerChildren,
|
||||||
|
}) => {
|
||||||
|
const projectId = useRequiredPathParam('projectId');
|
||||||
|
const featureId = useRequiredPathParam('featureId');
|
||||||
|
|
||||||
|
const editStrategyPath = formatEditStrategyPath(
|
||||||
|
projectId,
|
||||||
|
featureId,
|
||||||
|
environmentId,
|
||||||
|
strategy.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NewStrategyItemContainer
|
||||||
|
strategy={strategy}
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
orderNumber={orderNumber}
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
{headerChildren}
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(
|
||||||
|
otherEnvironments && otherEnvironments?.length > 0,
|
||||||
|
)}
|
||||||
|
show={() => (
|
||||||
|
<CopyStrategyIconMenu
|
||||||
|
environmentId={environmentId}
|
||||||
|
environments={otherEnvironments as string[]}
|
||||||
|
strategy={strategy}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<PermissionIconButton
|
||||||
|
permission={UPDATE_FEATURE_STRATEGY}
|
||||||
|
environmentId={environmentId}
|
||||||
|
projectId={projectId}
|
||||||
|
component={Link}
|
||||||
|
to={editStrategyPath}
|
||||||
|
tooltipProps={{
|
||||||
|
title: 'Edit strategy',
|
||||||
|
}}
|
||||||
|
data-testid={`STRATEGY_EDIT-${strategy.name}`}
|
||||||
|
>
|
||||||
|
<Edit />
|
||||||
|
</PermissionIconButton>
|
||||||
|
<MenuStrategyRemove
|
||||||
|
projectId={projectId}
|
||||||
|
featureId={featureId}
|
||||||
|
environmentId={environmentId}
|
||||||
|
strategy={strategy}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StrategyExecution strategy={strategy} />
|
||||||
|
|
||||||
|
{strategy.variants &&
|
||||||
|
strategy.variants.length > 0 &&
|
||||||
|
(strategy.disabled ? (
|
||||||
|
<Box sx={{ opacity: '0.5' }}>
|
||||||
|
<SplitPreviewSlider variants={strategy.variants} />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<SplitPreviewSlider variants={strategy.variants} />
|
||||||
|
))}
|
||||||
|
</NewStrategyItemContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -2,7 +2,7 @@ import { type FC, Fragment, useMemo } from 'react';
|
|||||||
import { Alert, Box, Chip, Link, styled } from '@mui/material';
|
import { Alert, Box, Chip, Link, styled } from '@mui/material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle';
|
import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
import { ConstraintItem } from './ConstraintItem/ConstraintItem';
|
import { ConstraintItem } from './ConstraintItem/ConstraintItem';
|
||||||
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
|
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
|
||||||
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
||||||
|
@ -10,10 +10,10 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
|||||||
import { StrategyExecution } from './StrategyExecution/StrategyExecution';
|
import { StrategyExecution } from './StrategyExecution/StrategyExecution';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMenu';
|
import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMenu';
|
||||||
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
|
|
||||||
import MenuStrategyRemove from './MenuStrategyRemove/MenuStrategyRemove';
|
import MenuStrategyRemove from './MenuStrategyRemove/MenuStrategyRemove';
|
||||||
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';
|
||||||
interface IStrategyItemProps {
|
interface IStrategyItemProps {
|
||||||
environmentId: string;
|
environmentId: string;
|
||||||
strategy: IFeatureStrategy;
|
strategy: IFeatureStrategy;
|
||||||
@ -44,7 +44,7 @@ export const StrategyItem: FC<IStrategyItemProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StrategyItemContainer
|
<NewStrategyItemContainer
|
||||||
strategy={strategy}
|
strategy={strategy}
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
@ -97,6 +97,6 @@ export const StrategyItem: FC<IStrategyItemProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<SplitPreviewSlider variants={strategy.variants} />
|
<SplitPreviewSlider variants={strategy.variants} />
|
||||||
))}
|
))}
|
||||||
</StrategyItemContainer>
|
</NewStrategyItemContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,6 @@ import type {
|
|||||||
IFeatureEnvironment,
|
IFeatureEnvironment,
|
||||||
IFeatureEnvironmentMetrics,
|
IFeatureEnvironmentMetrics,
|
||||||
} from 'interfaces/featureToggle';
|
} from 'interfaces/featureToggle';
|
||||||
import EnvironmentAccordionBody from './EnvironmentAccordionBody/EnvironmentAccordionBody';
|
|
||||||
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
|
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
|
||||||
import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds';
|
import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
@ -14,6 +13,7 @@ import FeatureOverviewEnvironmentMetrics from './EnvironmentHeader/FeatureOvervi
|
|||||||
import { FeatureOverviewEnvironmentToggle } from './EnvironmentHeader/FeatureOverviewEnvironmentToggle/FeatureOverviewEnvironmentToggle';
|
import { FeatureOverviewEnvironmentToggle } from './EnvironmentHeader/FeatureOverviewEnvironmentToggle/FeatureOverviewEnvironmentToggle';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { IReleasePlan } from 'interfaces/releasePlans';
|
import type { IReleasePlan } from 'interfaces/releasePlans';
|
||||||
|
import { EnvironmentAccordionBody as NewEnvironmentAccordionBody } from './EnvironmentAccordionBody/EnvironmentAccordionBody';
|
||||||
|
|
||||||
const StyledFeatureOverviewEnvironment = styled('div')(({ theme }) => ({
|
const StyledFeatureOverviewEnvironment = styled('div')(({ theme }) => ({
|
||||||
borderRadius: theme.shape.borderRadiusLarge,
|
borderRadius: theme.shape.borderRadiusLarge,
|
||||||
@ -32,15 +32,12 @@ const StyledAccordion = styled(Accordion)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
|
const NewStyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
|
||||||
padding: 0,
|
padding: 0,
|
||||||
background: theme.palette.envAccordion.expanded,
|
background: theme.palette.background.elevation1,
|
||||||
borderBottomLeftRadius: theme.shape.borderRadiusLarge,
|
borderBottomLeftRadius: theme.shape.borderRadiusLarge,
|
||||||
borderBottomRightRadius: theme.shape.borderRadiusLarge,
|
borderBottomRightRadius: theme.shape.borderRadiusLarge,
|
||||||
boxShadow: theme.boxShadows.accordionFooter,
|
boxShadow: theme.boxShadows.accordionFooter,
|
||||||
[theme.breakpoints.down('md')]: {
|
|
||||||
padding: theme.spacing(2, 1),
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledAccordionFooter = styled('footer')(({ theme }) => ({
|
const StyledAccordionFooter = styled('footer')(({ theme }) => ({
|
||||||
@ -55,7 +52,6 @@ const StyledAccordionFooter = styled('footer')(({ theme }) => ({
|
|||||||
const StyledEnvironmentAccordionContainer = styled('div')(({ theme }) => ({
|
const StyledEnvironmentAccordionContainer = styled('div')(({ theme }) => ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
padding: theme.spacing(3, 3, 1),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
type FeatureOverviewEnvironmentProps = {
|
type FeatureOverviewEnvironmentProps = {
|
||||||
@ -112,9 +108,9 @@ export const FeatureOverviewEnvironment = ({
|
|||||||
collapsed={!hasActivations}
|
collapsed={!hasActivations}
|
||||||
/>
|
/>
|
||||||
</EnvironmentHeader>
|
</EnvironmentHeader>
|
||||||
<StyledAccordionDetails>
|
<NewStyledAccordionDetails>
|
||||||
<StyledEnvironmentAccordionContainer>
|
<StyledEnvironmentAccordionContainer>
|
||||||
<EnvironmentAccordionBody
|
<NewEnvironmentAccordionBody
|
||||||
featureEnvironment={environment}
|
featureEnvironment={environment}
|
||||||
isDisabled={!environment.enabled}
|
isDisabled={!environment.enabled}
|
||||||
otherEnvironments={otherEnvironments}
|
otherEnvironments={otherEnvironments}
|
||||||
@ -131,7 +127,7 @@ export const FeatureOverviewEnvironment = ({
|
|||||||
<UpgradeChangeRequests />
|
<UpgradeChangeRequests />
|
||||||
) : null}
|
) : null}
|
||||||
</StyledAccordionFooter>
|
</StyledAccordionFooter>
|
||||||
</StyledAccordionDetails>
|
</NewStyledAccordionDetails>
|
||||||
</StyledAccordion>
|
</StyledAccordion>
|
||||||
</StyledFeatureOverviewEnvironment>
|
</StyledFeatureOverviewEnvironment>
|
||||||
);
|
);
|
||||||
|
@ -13,7 +13,7 @@ import { getFeatureMetrics } from 'utils/getFeatureMetrics';
|
|||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
|
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
|
||||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
import EnvironmentAccordionBody from './EnvironmentAccordionBody/EnvironmentAccordionBody';
|
import EnvironmentAccordionBody from './EnvironmentAccordionBody/LegacyEnvironmentAccordionBody';
|
||||||
import { EnvironmentFooter } from './EnvironmentFooter/EnvironmentFooter';
|
import { EnvironmentFooter } from './EnvironmentFooter/EnvironmentFooter';
|
||||||
import FeatureOverviewEnvironmentMetrics from './EnvironmentHeader/FeatureOverviewEnvironmentMetrics/LegacyFeatureOverviewEnvironmentMetrics';
|
import FeatureOverviewEnvironmentMetrics from './EnvironmentHeader/FeatureOverviewEnvironmentMetrics/LegacyFeatureOverviewEnvironmentMetrics';
|
||||||
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
|
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
import { SegmentItem } from '../../../../common/SegmentItem/SegmentItem';
|
import { SegmentItem } from '../../../../common/SegmentItem/SegmentItem';
|
||||||
import type { ISegment } from 'interfaces/segment';
|
import type { ISegment } from 'interfaces/segment';
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
import type { IReleasePlanMilestone } from 'interfaces/releasePlans';
|
import type { IReleasePlanMilestone } from 'interfaces/releasePlans';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { ReleasePlanMilestoneStrategy } from './ReleasePlanMilestoneStrategy';
|
import { ReleasePlanMilestoneStrategy } from './ReleasePlanMilestoneStrategy';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
import {
|
import {
|
||||||
ReleasePlanMilestoneStatus,
|
ReleasePlanMilestoneStatus,
|
||||||
type MilestoneStatus,
|
type MilestoneStatus,
|
||||||
|
@ -5,7 +5,7 @@ import type {
|
|||||||
} from 'openapi';
|
} from 'openapi';
|
||||||
import { objectId } from 'utils/objectId';
|
import { objectId } from 'utils/objectId';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import { ConstraintAccordionView } from 'component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView';
|
import { ConstraintAccordionView } from 'component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView';
|
||||||
import { ConstraintError } from './ConstraintError/ConstraintError';
|
import { ConstraintError } from './ConstraintError/ConstraintError';
|
||||||
|
@ -2,7 +2,7 @@ import { Fragment, type VFC } from 'react';
|
|||||||
import type { PlaygroundConstraintSchema } from 'openapi';
|
import type { PlaygroundConstraintSchema } from 'openapi';
|
||||||
import { objectId } from 'utils/objectId';
|
import { objectId } from 'utils/objectId';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import { ConstraintAccordionView } from 'component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView';
|
import { ConstraintAccordionView } from 'component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView';
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
parseParameterStrings,
|
parseParameterStrings,
|
||||||
} from 'utils/parseParameter';
|
} from 'utils/parseParameter';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
|
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
|
||||||
import { CustomParameterItem } from './CustomParameterItem/CustomParameterItem';
|
import { CustomParameterItem } from './CustomParameterItem/CustomParameterItem';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Fragment, type VFC } from 'react';
|
import { Fragment, type VFC } from 'react';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type {
|
import type {
|
||||||
PlaygroundRequestSchema,
|
PlaygroundRequestSchema,
|
||||||
|
@ -2,7 +2,7 @@ import { Fragment, type VFC } from 'react';
|
|||||||
import type { PlaygroundSegmentSchema, PlaygroundRequestSchema } from 'openapi';
|
import type { PlaygroundSegmentSchema, PlaygroundRequestSchema } from 'openapi';
|
||||||
import { ConstraintExecution } from '../ConstraintExecution/ConstraintExecution';
|
import { ConstraintExecution } from '../ConstraintExecution/ConstraintExecution';
|
||||||
import CancelOutlined from '@mui/icons-material/CancelOutlined';
|
import CancelOutlined from '@mui/icons-material/CancelOutlined';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
import { styled, Typography } from '@mui/material';
|
import { styled, Typography } from '@mui/material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { SegmentItem } from 'component/common/SegmentItem/SegmentItem';
|
import { SegmentItem } from 'component/common/SegmentItem/SegmentItem';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Fragment, type VFC } from 'react';
|
import { Fragment, type VFC } from 'react';
|
||||||
import type { PlaygroundSegmentSchema } from 'openapi';
|
import type { PlaygroundSegmentSchema } from 'openapi';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { SegmentItem } from 'component/common/SegmentItem/SegmentItem';
|
import { SegmentItem } from 'component/common/SegmentItem/SegmentItem';
|
||||||
import { ConstraintExecutionWithoutResults } from '../ConstraintExecution/ConstraintExecutionWithoutResults';
|
import { ConstraintExecutionWithoutResults } from '../ConstraintExecution/ConstraintExecutionWithoutResults';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Fragment, type VFC } from 'react';
|
import { Fragment, type VFC } from 'react';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type {
|
import type {
|
||||||
PlaygroundRequestSchema,
|
PlaygroundRequestSchema,
|
||||||
|
@ -7,7 +7,7 @@ import type {
|
|||||||
} from 'openapi';
|
} from 'openapi';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { FeatureStrategyItem } from './StrategyItem/FeatureStrategyItem';
|
import { FeatureStrategyItem } from './StrategyItem/FeatureStrategyItem';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
|
|
||||||
const StyledAlertWrapper = styled('div')(({ theme }) => ({
|
const StyledAlertWrapper = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
@ -3,7 +3,7 @@ import { type DragEventHandler, type RefObject, useRef } from 'react';
|
|||||||
import { Box, IconButton } from '@mui/material';
|
import { Box, IconButton } from '@mui/material';
|
||||||
import Edit from '@mui/icons-material/Edit';
|
import Edit from '@mui/icons-material/Edit';
|
||||||
import Delete from '@mui/icons-material/DeleteOutlined';
|
import Delete from '@mui/icons-material/DeleteOutlined';
|
||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
|
import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator';
|
||||||
import { MilestoneStrategyItem } from './MilestoneStrategyItem';
|
import { MilestoneStrategyItem } from './MilestoneStrategyItem';
|
||||||
|
|
||||||
interface IMilestoneStrategyDraggableItemProps {
|
interface IMilestoneStrategyDraggableItemProps {
|
||||||
|
Loading…
Reference in New Issue
Block a user