mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Feat/disabled strategies (#5930)
This PR makes disabled strategies more prominent in the UI: <img width="1031" alt="Skjermbilde 2024-01-17 kl 11 26 11" src="https://github.com/Unleash/unleash/assets/16081982/4a07c0aa-8f86-4854-829e-1088abecfb4e">
This commit is contained in:
		
							parent
							
								
									ee08bd8d42
								
							
						
					
					
						commit
						1deee10317
					
				@ -12,6 +12,7 @@ export const ConstraintIcon: VFC<IConstraintIconProps> = ({
 | 
			
		||||
    disabled,
 | 
			
		||||
}) => (
 | 
			
		||||
    <Box
 | 
			
		||||
        className='constraint-icon-container'
 | 
			
		||||
        sx={(theme) => ({
 | 
			
		||||
            backgroundColor: disabled
 | 
			
		||||
                ? theme.palette.neutral.border
 | 
			
		||||
@ -24,6 +25,7 @@ export const ConstraintIcon: VFC<IConstraintIconProps> = ({
 | 
			
		||||
        })}
 | 
			
		||||
    >
 | 
			
		||||
        <TrackChanges
 | 
			
		||||
            className='constraint-icon'
 | 
			
		||||
            sx={(theme) => ({
 | 
			
		||||
                fill: theme.palette.common.white,
 | 
			
		||||
                display: 'block',
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ const StyledContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    lineHeight: 1.25,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledName = styled('div', {
 | 
			
		||||
const StyledName = styled('p', {
 | 
			
		||||
    shouldForwardProp: (prop) => prop !== 'disabled',
 | 
			
		||||
})<{ disabled: boolean }>(({ theme, disabled }) => ({
 | 
			
		||||
    fontSize: theme.fontSizes.smallBody,
 | 
			
		||||
@ -23,7 +23,7 @@ const StyledName = styled('div', {
 | 
			
		||||
    color: disabled ? theme.palette.text.secondary : theme.palette.text.primary,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledText = styled('div', {
 | 
			
		||||
const StyledText = styled('p', {
 | 
			
		||||
    shouldForwardProp: (prop) => prop !== 'disabled',
 | 
			
		||||
})<{ disabled: boolean }>(({ theme, disabled }) => ({
 | 
			
		||||
    fontSize: theme.fontSizes.smallerBody,
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ const StyledContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    lineHeight: 1.25,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledName = styled('div', {
 | 
			
		||||
const StyledName = styled('p', {
 | 
			
		||||
    shouldForwardProp: (prop) => prop !== 'disabled',
 | 
			
		||||
})<{ disabled: boolean }>(({ theme, disabled }) => ({
 | 
			
		||||
    fontSize: theme.fontSizes.smallBody,
 | 
			
		||||
@ -23,7 +23,7 @@ const StyledName = styled('div', {
 | 
			
		||||
    color: disabled ? theme.palette.text.secondary : theme.palette.text.primary,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledText = styled('div', {
 | 
			
		||||
const StyledText = styled('p', {
 | 
			
		||||
    shouldForwardProp: (prop) => prop !== 'disabled',
 | 
			
		||||
})<{ disabled: boolean }>(({ theme, disabled }) => ({
 | 
			
		||||
    fontSize: theme.fontSizes.smallerBody,
 | 
			
		||||
 | 
			
		||||
@ -4,11 +4,13 @@ import { CSSProperties } from 'react';
 | 
			
		||||
interface IPercentageCircleProps {
 | 
			
		||||
    percentage: number;
 | 
			
		||||
    size?: `${number}rem`;
 | 
			
		||||
    disabled?: boolean | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const PercentageCircle = ({
 | 
			
		||||
    percentage,
 | 
			
		||||
    size = '4rem',
 | 
			
		||||
    disabled = false,
 | 
			
		||||
}: IPercentageCircleProps) => {
 | 
			
		||||
    const theme = useTheme();
 | 
			
		||||
 | 
			
		||||
@ -27,6 +29,10 @@ const PercentageCircle = ({
 | 
			
		||||
    const r = 100 / (2 * Math.PI);
 | 
			
		||||
    const d = 2 * r;
 | 
			
		||||
 | 
			
		||||
    const color = disabled
 | 
			
		||||
        ? theme.palette.neutral.border
 | 
			
		||||
        : theme.palette.primary.light;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <svg viewBox={`0 0 ${d} ${d}`} style={style} aria-hidden>
 | 
			
		||||
            <title>A circle progress bar with {percentage}% completion.</title>
 | 
			
		||||
@ -35,7 +41,7 @@ const PercentageCircle = ({
 | 
			
		||||
                cx={r}
 | 
			
		||||
                cy={r}
 | 
			
		||||
                fill='none'
 | 
			
		||||
                stroke={theme.palette.primary.light}
 | 
			
		||||
                stroke={color}
 | 
			
		||||
                strokeWidth={d}
 | 
			
		||||
                strokeDasharray={`${percentage} 100`}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
@ -16,15 +16,16 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
 | 
			
		||||
interface ISegmentItemProps {
 | 
			
		||||
    segment: Partial<ISegment>;
 | 
			
		||||
    isExpanded?: boolean;
 | 
			
		||||
    disabled?: boolean;
 | 
			
		||||
    disabled?: boolean | null;
 | 
			
		||||
    constraintList?: JSX.Element;
 | 
			
		||||
    headerContent?: JSX.Element;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const StyledAccordion = styled(Accordion)(({ theme }) => ({
 | 
			
		||||
const StyledAccordion = styled(Accordion, {
 | 
			
		||||
    shouldForwardProp: (prop) => prop !== 'isDisabled',
 | 
			
		||||
})<{ isDisabled: boolean | null }>(({ theme, isDisabled }) => ({
 | 
			
		||||
    border: `1px solid ${theme.palette.divider}`,
 | 
			
		||||
    borderRadius: theme.shape.borderRadiusMedium,
 | 
			
		||||
    backgroundColor: theme.palette.background.paper,
 | 
			
		||||
    boxShadow: 'none',
 | 
			
		||||
    margin: 0,
 | 
			
		||||
    transition: 'all 0.1s ease',
 | 
			
		||||
@ -32,6 +33,9 @@ const StyledAccordion = styled(Accordion)(({ theme }) => ({
 | 
			
		||||
        opacity: '0 !important',
 | 
			
		||||
    },
 | 
			
		||||
    '&.Mui-expanded': { backgroundColor: theme.palette.neutral.light },
 | 
			
		||||
    backgroundColor: isDisabled
 | 
			
		||||
        ? theme.palette.envAccordion.disabled
 | 
			
		||||
        : theme.palette.background.paper,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
 | 
			
		||||
@ -52,7 +56,7 @@ const StyledLink = styled(Link)(({ theme }) => ({
 | 
			
		||||
}));
 | 
			
		||||
const StyledText = styled('span', {
 | 
			
		||||
    shouldForwardProp: (prop) => prop !== 'disabled',
 | 
			
		||||
})<{ disabled: boolean }>(({ theme, disabled }) => ({
 | 
			
		||||
})<{ disabled: boolean | null }>(({ theme, disabled }) => ({
 | 
			
		||||
    color: disabled ? theme.palette.text.secondary : 'inherit',
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
@ -66,7 +70,7 @@ export const SegmentItem: VFC<ISegmentItemProps> = ({
 | 
			
		||||
    const [isOpen, setIsOpen] = useState(isExpanded || false);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <StyledAccordion expanded={isOpen}>
 | 
			
		||||
        <StyledAccordion isDisabled={disabled}>
 | 
			
		||||
            <StyledAccordionSummary id={`segment-accordion-${segment.id}`}>
 | 
			
		||||
                <DonutLarge
 | 
			
		||||
                    sx={(theme) => ({
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,6 @@ import { StrategySeparator } from 'component/common/StrategySeparator/StrategySe
 | 
			
		||||
import { ConstraintItem } from './ConstraintItem/ConstraintItem';
 | 
			
		||||
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
 | 
			
		||||
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
 | 
			
		||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
 | 
			
		||||
import { FeatureOverviewSegment } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment';
 | 
			
		||||
import { ConstraintAccordionList } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList';
 | 
			
		||||
import {
 | 
			
		||||
@ -23,6 +22,25 @@ interface IStrategyExecutionProps {
 | 
			
		||||
    strategy: IFeatureStrategyPayload | CreateFeatureStrategySchema;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const StyledContainer = styled(Box, {
 | 
			
		||||
    shouldForwardProp: (prop) => prop !== 'disabled',
 | 
			
		||||
})<{ disabled?: boolean | null }>(({ theme, disabled }) => ({
 | 
			
		||||
    '& p, & span, & h1, & h2, & h3, & h4, & h5, & h6': {
 | 
			
		||||
        color: disabled ? theme.palette.neutral.main : 'inherit',
 | 
			
		||||
    },
 | 
			
		||||
    '.constraint-icon-container': {
 | 
			
		||||
        backgroundColor: disabled
 | 
			
		||||
            ? theme.palette.neutral.border
 | 
			
		||||
            : theme.palette.primary.light,
 | 
			
		||||
        borderRadius: '50%',
 | 
			
		||||
    },
 | 
			
		||||
    '.constraint-icon': {
 | 
			
		||||
        fill: disabled
 | 
			
		||||
            ? theme.palette.neutral.light
 | 
			
		||||
            : theme.palette.common.white,
 | 
			
		||||
    },
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const NoItems: VFC = () => (
 | 
			
		||||
    <Box sx={{ px: 3, color: 'text.disabled' }}>
 | 
			
		||||
        This strategy does not have constraints or parameters.
 | 
			
		||||
@ -44,7 +62,6 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
 | 
			
		||||
}) => {
 | 
			
		||||
    const { parameters, constraints = [] } = strategy;
 | 
			
		||||
    const { strategies } = useStrategies();
 | 
			
		||||
    const { uiConfig } = useUiConfig();
 | 
			
		||||
    const { segments } = useSegments();
 | 
			
		||||
    const strategySegments = segments?.filter((segment) => {
 | 
			
		||||
        return strategy.segments?.includes(segment.id);
 | 
			
		||||
@ -63,6 +80,8 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
 | 
			
		||||
                case 'Rollout': {
 | 
			
		||||
                    const percentage = parseParameterNumber(parameters[key]);
 | 
			
		||||
 | 
			
		||||
                    const badgeType = strategy.disabled ? 'neutral' : 'success';
 | 
			
		||||
 | 
			
		||||
                    return (
 | 
			
		||||
                        <StyledValueContainer
 | 
			
		||||
                            sx={{ display: 'flex', alignItems: 'center' }}
 | 
			
		||||
@ -71,15 +90,18 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
 | 
			
		||||
                                <PercentageCircle
 | 
			
		||||
                                    percentage={percentage}
 | 
			
		||||
                                    size='2rem'
 | 
			
		||||
                                    disabled={strategy.disabled}
 | 
			
		||||
                                />
 | 
			
		||||
                            </Box>
 | 
			
		||||
                            <div>
 | 
			
		||||
                                <Badge color='success'>{percentage}%</Badge> of
 | 
			
		||||
                                your base{' '}
 | 
			
		||||
                                <Badge color={badgeType}>{percentage}%</Badge>{' '}
 | 
			
		||||
                                <span>of your base</span>{' '}
 | 
			
		||||
                                <span>
 | 
			
		||||
                                    {constraints.length > 0
 | 
			
		||||
                                        ? 'who match constraints'
 | 
			
		||||
                                        : ''}{' '}
 | 
			
		||||
                                    is included.
 | 
			
		||||
                                </span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </StyledValueContainer>
 | 
			
		||||
                    );
 | 
			
		||||
@ -109,7 +131,7 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
 | 
			
		||||
                    return null;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }, [parameters, definition, constraints]);
 | 
			
		||||
    }, [parameters, definition, constraints, strategy.disabled]);
 | 
			
		||||
 | 
			
		||||
    const customStrategyList = useMemo(() => {
 | 
			
		||||
        if (!parameters || !definition?.editable) return null;
 | 
			
		||||
@ -252,7 +274,10 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
 | 
			
		||||
 | 
			
		||||
    const listItems = [
 | 
			
		||||
        strategySegments && strategySegments.length > 0 && (
 | 
			
		||||
            <FeatureOverviewSegment segments={strategySegments} />
 | 
			
		||||
            <FeatureOverviewSegment
 | 
			
		||||
                segments={strategySegments}
 | 
			
		||||
                disabled={strategy.disabled}
 | 
			
		||||
            />
 | 
			
		||||
        ),
 | 
			
		||||
        constraints.length > 0 && (
 | 
			
		||||
            <ConstraintAccordionList
 | 
			
		||||
@ -276,7 +301,7 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
 | 
			
		||||
        <ConditionallyRender
 | 
			
		||||
            condition={listItems.length > 0}
 | 
			
		||||
            show={
 | 
			
		||||
                <>
 | 
			
		||||
                <StyledContainer disabled={Boolean(strategy.disabled)}>
 | 
			
		||||
                    {listItems.map((item, index) => (
 | 
			
		||||
                        // biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
 | 
			
		||||
                        <Fragment key={index}>
 | 
			
		||||
@ -287,7 +312,7 @@ export const StrategyExecution: VFC<IStrategyExecutionProps> = ({
 | 
			
		||||
                            {item}
 | 
			
		||||
                        </Fragment>
 | 
			
		||||
                    ))}
 | 
			
		||||
                </>
 | 
			
		||||
                </StyledContainer>
 | 
			
		||||
            }
 | 
			
		||||
            elseShow={<NoItems />}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMen
 | 
			
		||||
import { StrategyItemContainer } from 'component/common/StrategyItemContainer/StrategyItemContainer';
 | 
			
		||||
import MenuStrategyRemove from './MenuStrategyRemove/MenuStrategyRemove';
 | 
			
		||||
import SplitPreviewSlider from 'component/feature/StrategyTypes/SplitPreviewSlider/SplitPreviewSlider';
 | 
			
		||||
 | 
			
		||||
import { Box } from '@mui/material';
 | 
			
		||||
interface IStrategyItemProps {
 | 
			
		||||
    environmentId: string;
 | 
			
		||||
    strategy: IFeatureStrategy;
 | 
			
		||||
@ -87,9 +87,16 @@ export const StrategyItem: FC<IStrategyItemProps> = ({
 | 
			
		||||
            }
 | 
			
		||||
        >
 | 
			
		||||
            <StrategyExecution strategy={strategy} />
 | 
			
		||||
            {strategy.variants ? (
 | 
			
		||||
 | 
			
		||||
            {strategy.variants &&
 | 
			
		||||
                strategy.variants.length > 0 &&
 | 
			
		||||
                (strategy.disabled ? (
 | 
			
		||||
                    <Box sx={{ opacity: '0.5' }}>
 | 
			
		||||
                        <SplitPreviewSlider variants={strategy.variants} />
 | 
			
		||||
            ) : null}
 | 
			
		||||
                    </Box>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <SplitPreviewSlider variants={strategy.variants} />
 | 
			
		||||
                ))}
 | 
			
		||||
        </StrategyItemContainer>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -6,10 +6,12 @@ import { ISegment } from 'interfaces/segment';
 | 
			
		||||
 | 
			
		||||
interface IFeatureOverviewSegmentProps {
 | 
			
		||||
    segments?: ISegment[];
 | 
			
		||||
    disabled?: boolean | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const FeatureOverviewSegment = ({
 | 
			
		||||
    segments,
 | 
			
		||||
    disabled = false,
 | 
			
		||||
}: IFeatureOverviewSegmentProps) => {
 | 
			
		||||
    if (!segments || segments.length === 0) {
 | 
			
		||||
        return null;
 | 
			
		||||
@ -23,7 +25,7 @@ export const FeatureOverviewSegment = ({
 | 
			
		||||
                        condition={index > 0}
 | 
			
		||||
                        show={<StrategySeparator text='AND' />}
 | 
			
		||||
                    />
 | 
			
		||||
                    <SegmentItem segment={segment} />
 | 
			
		||||
                    <SegmentItem segment={segment} disabled={disabled} />
 | 
			
		||||
                </Fragment>
 | 
			
		||||
            ))}
 | 
			
		||||
        </>
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@ exports[`renders an empty list correctly 1`] = `
 | 
			
		||||
                className="css-non55o"
 | 
			
		||||
              >
 | 
			
		||||
                <div
 | 
			
		||||
                  className="css-16ldy6v"
 | 
			
		||||
                  className="css-1om4ep4"
 | 
			
		||||
                >
 | 
			
		||||
                  <svg
 | 
			
		||||
                    aria-hidden={true}
 | 
			
		||||
 | 
			
		||||
@ -137,7 +137,7 @@ export const theme = {
 | 
			
		||||
            main: colors.grey[700],
 | 
			
		||||
            light: colors.grey[100],
 | 
			
		||||
            dark: colors.grey[800],
 | 
			
		||||
            border: colors.grey[400],
 | 
			
		||||
            border: colors.grey[500],
 | 
			
		||||
            contrastText: colors.grey[800], // Color used for text inside badge
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user