mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Visual updates to constraints (#1157)
This commit is contained in:
		
							parent
							
								
									7df59cf286
								
							
						
					
					
						commit
						c20aa300ce
					
				@ -52,10 +52,13 @@ export const useStyles = makeStyles()(theme => ({
 | 
			
		||||
    headerValuesContainerWrapper: {
 | 
			
		||||
        display: 'flex',
 | 
			
		||||
        alignItems: 'stretch',
 | 
			
		||||
        margin: 'auto 0',
 | 
			
		||||
    },
 | 
			
		||||
    headerValuesContainer: {
 | 
			
		||||
        display: 'flex',
 | 
			
		||||
        alignItems: 'stretch',
 | 
			
		||||
        justifyContent: 'stretch',
 | 
			
		||||
        margin: 'auto 0',
 | 
			
		||||
        flexDirection: 'column',
 | 
			
		||||
    },
 | 
			
		||||
    headerValues: {
 | 
			
		||||
        fontSize: theme.fontSizes.smallBody,
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,7 @@ export const useStyles = makeStyles()(theme => ({
 | 
			
		||||
        margin: '0.75rem 0 ',
 | 
			
		||||
    },
 | 
			
		||||
    customConstraintLabel: {
 | 
			
		||||
        marginBottom: theme.spacing(1),
 | 
			
		||||
        color: theme.palette.text.secondary,
 | 
			
		||||
    },
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import React, { forwardRef, useImperativeHandle } from 'react';
 | 
			
		||||
import React, { forwardRef, Fragment, useImperativeHandle } from 'react';
 | 
			
		||||
import { Button, Tooltip } from '@mui/material';
 | 
			
		||||
import { HelpOutline } from '@mui/icons-material';
 | 
			
		||||
import { IConstraint } from 'interfaces/strategy';
 | 
			
		||||
@ -10,11 +10,14 @@ import { objectId } from 'utils/objectId';
 | 
			
		||||
import { useStyles } from './ConstraintAccordionList.styles';
 | 
			
		||||
import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
 | 
			
		||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
 | 
			
		||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
 | 
			
		||||
 | 
			
		||||
interface IConstraintAccordionListProps {
 | 
			
		||||
    constraints: IConstraint[];
 | 
			
		||||
    setConstraints?: React.Dispatch<React.SetStateAction<IConstraint[]>>;
 | 
			
		||||
    showCreateButton?: boolean;
 | 
			
		||||
    /* Add "Custom constraints" title on the top - default `true` */
 | 
			
		||||
    showLabel?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ref methods exposed by this component.
 | 
			
		||||
@ -35,124 +38,138 @@ export const constraintAccordionListId = 'constraintAccordionListId';
 | 
			
		||||
export const ConstraintAccordionList = forwardRef<
 | 
			
		||||
    IConstraintAccordionListRef | undefined,
 | 
			
		||||
    IConstraintAccordionListProps
 | 
			
		||||
>(({ constraints, setConstraints, showCreateButton }, ref) => {
 | 
			
		||||
    const state = useWeakMap<IConstraint, IConstraintAccordionListItemState>();
 | 
			
		||||
    const { context } = useUnleashContext();
 | 
			
		||||
    const { classes: styles } = useStyles();
 | 
			
		||||
>(
 | 
			
		||||
    (
 | 
			
		||||
        { constraints, setConstraints, showCreateButton, showLabel = true },
 | 
			
		||||
        ref
 | 
			
		||||
    ) => {
 | 
			
		||||
        const state = useWeakMap<
 | 
			
		||||
            IConstraint,
 | 
			
		||||
            IConstraintAccordionListItemState
 | 
			
		||||
        >();
 | 
			
		||||
        const { context } = useUnleashContext();
 | 
			
		||||
        const { classes: styles } = useStyles();
 | 
			
		||||
 | 
			
		||||
    const addConstraint =
 | 
			
		||||
        setConstraints &&
 | 
			
		||||
        ((contextName: string) => {
 | 
			
		||||
            const constraint = createEmptyConstraint(contextName);
 | 
			
		||||
            state.set(constraint, { editing: true, new: true });
 | 
			
		||||
            setConstraints(prev => [...prev, constraint]);
 | 
			
		||||
        });
 | 
			
		||||
        const addConstraint =
 | 
			
		||||
            setConstraints &&
 | 
			
		||||
            ((contextName: string) => {
 | 
			
		||||
                const constraint = createEmptyConstraint(contextName);
 | 
			
		||||
                state.set(constraint, { editing: true, new: true });
 | 
			
		||||
                setConstraints(prev => [...prev, constraint]);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
    useImperativeHandle(ref, () => ({
 | 
			
		||||
        addConstraint,
 | 
			
		||||
    }));
 | 
			
		||||
        useImperativeHandle(ref, () => ({
 | 
			
		||||
            addConstraint,
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
    const onAdd =
 | 
			
		||||
        addConstraint &&
 | 
			
		||||
        (() => {
 | 
			
		||||
            addConstraint(context[0].name);
 | 
			
		||||
        });
 | 
			
		||||
        const onAdd =
 | 
			
		||||
            addConstraint &&
 | 
			
		||||
            (() => {
 | 
			
		||||
                addConstraint(context[0].name);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
    const onEdit =
 | 
			
		||||
        setConstraints &&
 | 
			
		||||
        ((constraint: IConstraint) => {
 | 
			
		||||
            state.set(constraint, { editing: true });
 | 
			
		||||
        });
 | 
			
		||||
        const onEdit =
 | 
			
		||||
            setConstraints &&
 | 
			
		||||
            ((constraint: IConstraint) => {
 | 
			
		||||
                state.set(constraint, { editing: true });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
    const onRemove =
 | 
			
		||||
        setConstraints &&
 | 
			
		||||
        ((index: number) => {
 | 
			
		||||
        const onRemove =
 | 
			
		||||
            setConstraints &&
 | 
			
		||||
            ((index: number) => {
 | 
			
		||||
                const constraint = constraints[index];
 | 
			
		||||
                state.set(constraint, {});
 | 
			
		||||
                setConstraints(
 | 
			
		||||
                    produce(draft => {
 | 
			
		||||
                        draft.splice(index, 1);
 | 
			
		||||
                    })
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        const onSave =
 | 
			
		||||
            setConstraints &&
 | 
			
		||||
            ((index: number, constraint: IConstraint) => {
 | 
			
		||||
                state.set(constraint, {});
 | 
			
		||||
                setConstraints(
 | 
			
		||||
                    produce(draft => {
 | 
			
		||||
                        draft[index] = constraint;
 | 
			
		||||
                    })
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        const onCancel = (index: number) => {
 | 
			
		||||
            const constraint = constraints[index];
 | 
			
		||||
            state.get(constraint)?.new && onRemove?.(index);
 | 
			
		||||
            state.set(constraint, {});
 | 
			
		||||
            setConstraints(
 | 
			
		||||
                produce(draft => {
 | 
			
		||||
                    draft.splice(index, 1);
 | 
			
		||||
                })
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    const onSave =
 | 
			
		||||
        setConstraints &&
 | 
			
		||||
        ((index: number, constraint: IConstraint) => {
 | 
			
		||||
            state.set(constraint, {});
 | 
			
		||||
            setConstraints(
 | 
			
		||||
                produce(draft => {
 | 
			
		||||
                    draft[index] = constraint;
 | 
			
		||||
                })
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        if (context.length === 0) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    const onCancel = (index: number) => {
 | 
			
		||||
        const constraint = constraints[index];
 | 
			
		||||
        state.get(constraint)?.new && onRemove?.(index);
 | 
			
		||||
        state.set(constraint, {});
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (context.length === 0) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={styles.container} id={constraintAccordionListId}>
 | 
			
		||||
            <ConditionallyRender
 | 
			
		||||
                condition={constraints && constraints.length > 0}
 | 
			
		||||
                show={
 | 
			
		||||
                    <p className={styles.customConstraintLabel}>
 | 
			
		||||
                        Custom constraints
 | 
			
		||||
                    </p>
 | 
			
		||||
                }
 | 
			
		||||
            />
 | 
			
		||||
            {constraints.map((constraint, index) => (
 | 
			
		||||
                <ConstraintAccordion
 | 
			
		||||
                    key={objectId(constraint)}
 | 
			
		||||
                    constraint={constraint}
 | 
			
		||||
                    onEdit={onEdit && onEdit.bind(null, constraint)}
 | 
			
		||||
                    onCancel={onCancel.bind(null, index)}
 | 
			
		||||
                    onDelete={onRemove && onRemove.bind(null, index)}
 | 
			
		||||
                    onSave={onSave && onSave.bind(null, index)}
 | 
			
		||||
                    editing={Boolean(state.get(constraint)?.editing)}
 | 
			
		||||
                    compact
 | 
			
		||||
        return (
 | 
			
		||||
            <div className={styles.container} id={constraintAccordionListId}>
 | 
			
		||||
                <ConditionallyRender
 | 
			
		||||
                    condition={
 | 
			
		||||
                        constraints && constraints.length > 0 && showLabel
 | 
			
		||||
                    }
 | 
			
		||||
                    show={
 | 
			
		||||
                        <p className={styles.customConstraintLabel}>
 | 
			
		||||
                            Custom constraints
 | 
			
		||||
                        </p>
 | 
			
		||||
                    }
 | 
			
		||||
                />
 | 
			
		||||
            ))}
 | 
			
		||||
            <ConditionallyRender
 | 
			
		||||
                condition={Boolean(showCreateButton && onAdd)}
 | 
			
		||||
                show={
 | 
			
		||||
                    <div>
 | 
			
		||||
                        <div className={styles.addCustomLabel}>
 | 
			
		||||
                            <p>Add any number of custom constraints</p>
 | 
			
		||||
                            <Tooltip
 | 
			
		||||
                                title="Help"
 | 
			
		||||
                                arrow
 | 
			
		||||
                                className={styles.helpWrapper}
 | 
			
		||||
                            >
 | 
			
		||||
                                <a
 | 
			
		||||
                                    href={
 | 
			
		||||
                                        'https://docs.getunleash.io/advanced/strategy_constraints'
 | 
			
		||||
                                    }
 | 
			
		||||
                                    target="_blank"
 | 
			
		||||
                                    rel="noopener noreferrer"
 | 
			
		||||
                {constraints.map((constraint, index) => (
 | 
			
		||||
                    <Fragment key={objectId(constraint)}>
 | 
			
		||||
                        <ConditionallyRender
 | 
			
		||||
                            condition={index > 0}
 | 
			
		||||
                            show={<StrategySeparator text="AND" />}
 | 
			
		||||
                        />
 | 
			
		||||
                        <ConstraintAccordion
 | 
			
		||||
                            constraint={constraint}
 | 
			
		||||
                            onEdit={onEdit && onEdit.bind(null, constraint)}
 | 
			
		||||
                            onCancel={onCancel.bind(null, index)}
 | 
			
		||||
                            onDelete={onRemove && onRemove.bind(null, index)}
 | 
			
		||||
                            onSave={onSave && onSave.bind(null, index)}
 | 
			
		||||
                            editing={Boolean(state.get(constraint)?.editing)}
 | 
			
		||||
                            compact
 | 
			
		||||
                        />
 | 
			
		||||
                    </Fragment>
 | 
			
		||||
                ))}
 | 
			
		||||
                <ConditionallyRender
 | 
			
		||||
                    condition={Boolean(showCreateButton && onAdd)}
 | 
			
		||||
                    show={
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <div className={styles.addCustomLabel}>
 | 
			
		||||
                                <p>Add any number of custom constraints</p>
 | 
			
		||||
                                <Tooltip
 | 
			
		||||
                                    title="Help"
 | 
			
		||||
                                    arrow
 | 
			
		||||
                                    className={styles.helpWrapper}
 | 
			
		||||
                                >
 | 
			
		||||
                                    <HelpOutline className={styles.help} />
 | 
			
		||||
                                </a>
 | 
			
		||||
                            </Tooltip>
 | 
			
		||||
                                    <a
 | 
			
		||||
                                        href={
 | 
			
		||||
                                            'https://docs.getunleash.io/advanced/strategy_constraints'
 | 
			
		||||
                                        }
 | 
			
		||||
                                        target="_blank"
 | 
			
		||||
                                        rel="noopener noreferrer"
 | 
			
		||||
                                    >
 | 
			
		||||
                                        <HelpOutline className={styles.help} />
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                </Tooltip>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <Button
 | 
			
		||||
                                type="button"
 | 
			
		||||
                                onClick={onAdd}
 | 
			
		||||
                                variant="outlined"
 | 
			
		||||
                                color="secondary"
 | 
			
		||||
                            >
 | 
			
		||||
                                Add custom constraint
 | 
			
		||||
                            </Button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <Button
 | 
			
		||||
                            type="button"
 | 
			
		||||
                            onClick={onAdd}
 | 
			
		||||
                            variant="outlined"
 | 
			
		||||
                            color="secondary"
 | 
			
		||||
                            sx={{ mb: 2 }}
 | 
			
		||||
                        >
 | 
			
		||||
                            Add custom constraint
 | 
			
		||||
                        </Button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                }
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
});
 | 
			
		||||
                    }
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ interface IStrategySeparatorProps {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const StyledContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    height: theme.spacing(2),
 | 
			
		||||
    height: theme.spacing(1),
 | 
			
		||||
    position: 'relative',
 | 
			
		||||
    width: '100%',
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
@ -256,7 +256,10 @@ export const StrategyExecution = ({ strategy }: IStrategyExecutionProps) => {
 | 
			
		||||
                condition={constraints.length > 0}
 | 
			
		||||
                show={
 | 
			
		||||
                    <>
 | 
			
		||||
                        <ConstraintAccordionList constraints={constraints} />
 | 
			
		||||
                        <ConstraintAccordionList
 | 
			
		||||
                            constraints={constraints}
 | 
			
		||||
                            showLabel={false}
 | 
			
		||||
                        />
 | 
			
		||||
                        <StrategySeparator text="AND" />
 | 
			
		||||
                    </>
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ export const useStyles = makeStyles()(theme => ({
 | 
			
		||||
        alignItems: 'center',
 | 
			
		||||
        borderBottom: `1px solid ${theme.palette.grey[300]}`,
 | 
			
		||||
        fontWeight: theme.typography.fontWeightMedium,
 | 
			
		||||
        fontSize: theme.fontSizes.smallBody,
 | 
			
		||||
    },
 | 
			
		||||
    icon: {
 | 
			
		||||
        fill: theme.palette.inactiveIcon,
 | 
			
		||||
 | 
			
		||||
@ -3,16 +3,18 @@ import { makeStyles } from 'tss-react/mui';
 | 
			
		||||
export const useStyles = makeStyles()(theme => ({
 | 
			
		||||
    container: {
 | 
			
		||||
        width: '100%',
 | 
			
		||||
        padding: '1rem',
 | 
			
		||||
        padding: theme.spacing(2, 3),
 | 
			
		||||
        display: 'flex',
 | 
			
		||||
        alignItems: 'center',
 | 
			
		||||
        justifyContent: 'flex-start',
 | 
			
		||||
        fontSize: theme.fontSizes.smallBody,
 | 
			
		||||
        backgroundColor: theme.palette.grey[200],
 | 
			
		||||
        border: `1px solid ${theme.palette.dividerAlternative}`,
 | 
			
		||||
        position: 'relative',
 | 
			
		||||
        borderRadius: '5px',
 | 
			
		||||
        textAlign: 'center',
 | 
			
		||||
    },
 | 
			
		||||
    link: {
 | 
			
		||||
        textDecoration: 'none',
 | 
			
		||||
        fontWeight: theme.fontWeight.bold,
 | 
			
		||||
        marginLeft: theme.spacing(1),
 | 
			
		||||
        '&:hover': {
 | 
			
		||||
            textDecoration: 'underline',
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
import { Fragment } from 'react';
 | 
			
		||||
import { Link } from 'react-router-dom';
 | 
			
		||||
import { DonutLarge } from '@mui/icons-material';
 | 
			
		||||
import { useStyles } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment.styles';
 | 
			
		||||
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
 | 
			
		||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
 | 
			
		||||
import { Link } from 'react-router-dom';
 | 
			
		||||
import { Fragment } from 'react';
 | 
			
		||||
 | 
			
		||||
interface IFeatureOverviewSegmentProps {
 | 
			
		||||
    strategyId: string;
 | 
			
		||||
@ -23,7 +24,7 @@ export const FeatureOverviewSegment = ({
 | 
			
		||||
            {segments.map(segment => (
 | 
			
		||||
                <Fragment key={segment.id}>
 | 
			
		||||
                    <div className={styles.container}>
 | 
			
		||||
                        Segment{' '}
 | 
			
		||||
                        <DonutLarge color="secondary" sx={{ mr: 1 }} /> Segment:{' '}
 | 
			
		||||
                        <Link
 | 
			
		||||
                            to={segmentPath(segment.id)}
 | 
			
		||||
                            className={styles.link}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user