mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	task: Add buttons for deleting/editing a constraint (#522)
* task: Add buttons for deleting/editing a constraint * task: Improve look and feel of constraints buttons - Make constraints fill their container - Move constraint buttons to material ui buttons - Move constraint buttons to top right of their container * fix: adjust positioning * fix: added project id to permissin button * fix: add correct permission * fix: update create feature path Co-authored-by: Simon Hornby <simon@getunleash.ai> Co-authored-by: Fredrik Oseberg <fredrik.no@gmail.com>
This commit is contained in:
		
							parent
							
								
									3e53a64fcf
								
							
						
					
					
						commit
						b85b326104
					
				@ -82,7 +82,9 @@ describe('feature toggle', () => {
 | 
			
		||||
 | 
			
		||||
        cy.get('[data-test=NAVIGATE_TO_CREATE_FEATURE').click();
 | 
			
		||||
 | 
			
		||||
        cy.intercept('POST', '/api/admin/features').as('createFeature');
 | 
			
		||||
        cy.intercept('POST', '/api/admin/projects/default/features').as(
 | 
			
		||||
            'createFeature'
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        cy.get("[data-test='CF_NAME_ID'").type(featureToggleName);
 | 
			
		||||
        cy.get("[data-test='CF_DESC_ID'").type('hellowrdada');
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,7 @@
 | 
			
		||||
    "test": "react-scripts test",
 | 
			
		||||
    "prepare": "yarn run build",
 | 
			
		||||
    "e2e": "yarn run cypress open --config baseUrl='http://localhost:3000' --env PASSWORD_AUTH=true,AUTH_TOKEN=$AUTH_TOKEN",
 | 
			
		||||
    "e2e:heroku": "yarn run cypress open --config baseUrl='http://localhost:3000' --env PASSWORD_AUTH=false,AUTH_TOKEN=$AUTH_TOKEN",
 | 
			
		||||
    "e2e:enterprise": "yarn run cypress open --config baseUrl='http://localhost:3000' --env PASSWORD_AUTH=true,ENTERPRISE=true,AUTH_TOKEN=$AUTH_TOKEN"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
 | 
			
		||||
@ -11,11 +11,21 @@ export const useStyles = makeStyles(theme => ({
 | 
			
		||||
        alignItems: 'center',
 | 
			
		||||
        justifyContent: 'center',
 | 
			
		||||
        padding: '0.1rem 0.5rem',
 | 
			
		||||
        border: `1px solid ${theme.palette.grey[300]}`,
 | 
			
		||||
        borderRadius: '5px',
 | 
			
		||||
        fontSize: theme.fontSizes.smallBody,
 | 
			
		||||
        backgroundColor: theme.palette.grey[200],
 | 
			
		||||
        margin: '0.5rem 0',
 | 
			
		||||
        position: 'relative',
 | 
			
		||||
        borderRadius: '5px',
 | 
			
		||||
    },
 | 
			
		||||
    constraintBtn: {
 | 
			
		||||
        color: theme.palette.primary.main,
 | 
			
		||||
        fontWeight: 'normal',
 | 
			
		||||
        marginBottom: '0.5rem',
 | 
			
		||||
    },
 | 
			
		||||
    btnContainer: {
 | 
			
		||||
        position: 'absolute',
 | 
			
		||||
        top: '6px',
 | 
			
		||||
        right: 0,
 | 
			
		||||
    },
 | 
			
		||||
    column: {
 | 
			
		||||
        flexDirection: 'column',
 | 
			
		||||
 | 
			
		||||
@ -1,31 +1,75 @@
 | 
			
		||||
import { Delete, Edit } from '@material-ui/icons';
 | 
			
		||||
import classnames from 'classnames';
 | 
			
		||||
import { useParams } from 'react-router';
 | 
			
		||||
import { IFeatureViewParams } from '../../../interfaces/params';
 | 
			
		||||
import { IConstraint } from '../../../interfaces/strategy';
 | 
			
		||||
import FeatureStrategiesSeparator from '../../feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesSeparator/FeatureStrategiesSeparator';
 | 
			
		||||
import { UPDATE_FEATURE } from '../../providers/AccessProvider/permissions';
 | 
			
		||||
import ConditionallyRender from '../ConditionallyRender';
 | 
			
		||||
import PermissionIconButton from '../PermissionIconButton/PermissionIconButton';
 | 
			
		||||
import StringTruncator from '../StringTruncator/StringTruncator';
 | 
			
		||||
import { useStyles } from './Constraint.styles';
 | 
			
		||||
 | 
			
		||||
interface IConstraintProps {
 | 
			
		||||
    constraint: IConstraint;
 | 
			
		||||
    className?: string;
 | 
			
		||||
    deleteCallback?: () => void;
 | 
			
		||||
    editCallback?: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Constraint = ({ constraint, className, ...rest }: IConstraintProps) => {
 | 
			
		||||
const Constraint = ({
 | 
			
		||||
    constraint,
 | 
			
		||||
    deleteCallback,
 | 
			
		||||
    editCallback,
 | 
			
		||||
    className,
 | 
			
		||||
    ...rest
 | 
			
		||||
}: IConstraintProps) => {
 | 
			
		||||
    const styles = useStyles();
 | 
			
		||||
    const { projectId } = useParams<IFeatureViewParams>();
 | 
			
		||||
 | 
			
		||||
    const classes = classnames(styles.constraint, {
 | 
			
		||||
        [styles.column]: constraint.values.length > 2,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const editable = !!(deleteCallback && editCallback);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={classes + ' ' + className} {...rest}>
 | 
			
		||||
            <StringTruncator text={constraint.contextName} maxWidth="125" />
 | 
			
		||||
            <FeatureStrategiesSeparator
 | 
			
		||||
                text={constraint.operator}
 | 
			
		||||
                maxWidth="none"
 | 
			
		||||
            <div className={classes + ' ' + className} {...rest}>
 | 
			
		||||
                <StringTruncator text={constraint.contextName} maxWidth="125" />
 | 
			
		||||
                <FeatureStrategiesSeparator
 | 
			
		||||
                    text={constraint.operator}
 | 
			
		||||
                    maxWidth="none"
 | 
			
		||||
                />
 | 
			
		||||
                <span className={styles.values}>
 | 
			
		||||
                    {constraint.values.join(', ')}
 | 
			
		||||
                </span>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <ConditionallyRender
 | 
			
		||||
                condition={editable}
 | 
			
		||||
                show={
 | 
			
		||||
                    <div className={styles.btnContainer}>
 | 
			
		||||
                        <PermissionIconButton
 | 
			
		||||
                            onClick={editCallback}
 | 
			
		||||
                            tooltip="Edit strategy"
 | 
			
		||||
                            permission={UPDATE_FEATURE}
 | 
			
		||||
                            projectId={projectId}
 | 
			
		||||
                        >
 | 
			
		||||
                            <Edit />
 | 
			
		||||
                        </PermissionIconButton>
 | 
			
		||||
 | 
			
		||||
                        <PermissionIconButton
 | 
			
		||||
                            onClick={deleteCallback}
 | 
			
		||||
                            tooltip="Delete strategy"
 | 
			
		||||
                            permission={UPDATE_FEATURE}
 | 
			
		||||
                            projectId={projectId}
 | 
			
		||||
                        >
 | 
			
		||||
                            <Delete />
 | 
			
		||||
                        </PermissionIconButton>
 | 
			
		||||
                    </div>
 | 
			
		||||
                }
 | 
			
		||||
            />
 | 
			
		||||
            <span className={styles.values}>
 | 
			
		||||
                {constraint.values.join(', ')}
 | 
			
		||||
            </span>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ import AccessContext from '../../../contexts/AccessContext';
 | 
			
		||||
 | 
			
		||||
interface IPermissionIconButtonProps extends OverridableComponent<any> {
 | 
			
		||||
    permission: string;
 | 
			
		||||
    Icon: React.ElementType;
 | 
			
		||||
    Icon?: React.ElementType;
 | 
			
		||||
    tooltip: string;
 | 
			
		||||
    onClick?: (e: any) => void;
 | 
			
		||||
    projectId?: string;
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,8 @@ export const useStyles = makeStyles(theme => ({
 | 
			
		||||
        marginBottom: '0.5rem',
 | 
			
		||||
    },
 | 
			
		||||
    accordionContainer: {
 | 
			
		||||
        width: '80%',
 | 
			
		||||
        width: '100%',
 | 
			
		||||
        paddingRight: '37px',
 | 
			
		||||
        [theme.breakpoints.down(800)]: {
 | 
			
		||||
            width: '100%',
 | 
			
		||||
        },
 | 
			
		||||
@ -20,7 +21,4 @@ export const useStyles = makeStyles(theme => ({
 | 
			
		||||
        marginTop: '0.5rem',
 | 
			
		||||
        fontSize: theme.fontSizes.smallBody,
 | 
			
		||||
    },
 | 
			
		||||
    constraintBody: {
 | 
			
		||||
        maxWidth: '350px',
 | 
			
		||||
    },
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,6 @@ import { useContext, useState } from 'react';
 | 
			
		||||
import ConditionallyRender from '../../../../../common/ConditionallyRender';
 | 
			
		||||
import useUiConfig from '../../../../../../hooks/api/getters/useUiConfig/useUiConfig';
 | 
			
		||||
import { C } from '../../../../../common/flags';
 | 
			
		||||
import { Button } from '@material-ui/core';
 | 
			
		||||
import { useStyles } from './FeatureStrategyAccordionBody.styles';
 | 
			
		||||
import Dialogue from '../../../../../common/Dialogue';
 | 
			
		||||
import DefaultStrategy from '../../common/DefaultStrategy/DefaultStrategy';
 | 
			
		||||
@ -20,6 +19,9 @@ import { ADD_CONSTRAINT_ID } from '../../../../../../testIds';
 | 
			
		||||
import AccessContext from '../../../../../../contexts/AccessContext';
 | 
			
		||||
import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions';
 | 
			
		||||
import Constraint from '../../../../../common/Constraint/Constraint';
 | 
			
		||||
import PermissionButton from '../../../../../common/PermissionButton/PermissionButton';
 | 
			
		||||
import { useParams } from 'react-router';
 | 
			
		||||
import { IFeatureViewParams } from '../../../../../../interfaces/params';
 | 
			
		||||
 | 
			
		||||
interface IFeatureStrategyAccordionBodyProps {
 | 
			
		||||
    strategy: IFeatureStrategy;
 | 
			
		||||
@ -40,6 +42,7 @@ const FeatureStrategyAccordionBody: React.FC<IFeatureStrategyAccordionBodyProps>
 | 
			
		||||
        setStrategyConstraints,
 | 
			
		||||
    }) => {
 | 
			
		||||
        const styles = useStyles();
 | 
			
		||||
        const { projectId } = useParams<IFeatureViewParams>();
 | 
			
		||||
        const [constraintError, setConstraintError] = useState({});
 | 
			
		||||
        const { strategies } = useStrategies();
 | 
			
		||||
        const { uiConfig } = useUiConfig();
 | 
			
		||||
@ -106,13 +109,25 @@ const FeatureStrategyAccordionBody: React.FC<IFeatureStrategyAccordionBodyProps>
 | 
			
		||||
                return (
 | 
			
		||||
                    <Constraint
 | 
			
		||||
                        constraint={constraint}
 | 
			
		||||
                        editCallback={() => {
 | 
			
		||||
                            setShowConstraints(true);
 | 
			
		||||
                        }}
 | 
			
		||||
                        deleteCallback={() => {
 | 
			
		||||
                            removeConstraint(index);
 | 
			
		||||
                        }}
 | 
			
		||||
                        key={`${constraint.contextName}-${index}`}
 | 
			
		||||
                        className={styles.constraintBody}
 | 
			
		||||
                    />
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const removeConstraint = (index: number) => {
 | 
			
		||||
            const updatedConstraints = [...constraints];
 | 
			
		||||
            updatedConstraints.splice(index, 1);
 | 
			
		||||
 | 
			
		||||
            updateConstraints(updatedConstraints);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const closeConstraintDialog = () => {
 | 
			
		||||
            setShowConstraints(false);
 | 
			
		||||
            const filteredConstraints = constraints.filter(constraint => {
 | 
			
		||||
@ -137,18 +152,17 @@ const FeatureStrategyAccordionBody: React.FC<IFeatureStrategyAccordionBodyProps>
 | 
			
		||||
                                Constraints
 | 
			
		||||
                            </p>
 | 
			
		||||
                            {renderConstraints()}
 | 
			
		||||
                            <ConditionallyRender
 | 
			
		||||
                                condition={hasAccess(UPDATE_FEATURE)}
 | 
			
		||||
                                show={
 | 
			
		||||
                                    <Button
 | 
			
		||||
                                        className={styles.addConstraintBtn}
 | 
			
		||||
                                        onClick={toggleConstraints}
 | 
			
		||||
                                        data-test={ADD_CONSTRAINT_ID}
 | 
			
		||||
                                    >
 | 
			
		||||
                                        + Edit constraints
 | 
			
		||||
                                    </Button>
 | 
			
		||||
                                }
 | 
			
		||||
                            />
 | 
			
		||||
 | 
			
		||||
                            <PermissionButton
 | 
			
		||||
                                className={styles.addConstraintBtn}
 | 
			
		||||
                                onClick={toggleConstraints}
 | 
			
		||||
                                variant={'text'}
 | 
			
		||||
                                data-test={ADD_CONSTRAINT_ID}
 | 
			
		||||
                                permission={UPDATE_FEATURE}
 | 
			
		||||
                                projectId={projectId}
 | 
			
		||||
                            >
 | 
			
		||||
                                + Add constraints
 | 
			
		||||
                            </PermissionButton>
 | 
			
		||||
                        </>
 | 
			
		||||
                    }
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user