mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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