mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-15 17:50:48 +02: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.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_NAME_ID'").type(featureToggleName);
|
||||||
cy.get("[data-test='CF_DESC_ID'").type('hellowrdada');
|
cy.get("[data-test='CF_DESC_ID'").type('hellowrdada');
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"prepare": "yarn run build",
|
"prepare": "yarn run build",
|
||||||
"e2e": "yarn run cypress open --config baseUrl='http://localhost:3000' --env PASSWORD_AUTH=true,AUTH_TOKEN=$AUTH_TOKEN",
|
"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"
|
"e2e:enterprise": "yarn run cypress open --config baseUrl='http://localhost:3000' --env PASSWORD_AUTH=true,ENTERPRISE=true,AUTH_TOKEN=$AUTH_TOKEN"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -11,11 +11,21 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
padding: '0.1rem 0.5rem',
|
padding: '0.1rem 0.5rem',
|
||||||
border: `1px solid ${theme.palette.grey[300]}`,
|
|
||||||
borderRadius: '5px',
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
fontSize: theme.fontSizes.smallBody,
|
||||||
backgroundColor: theme.palette.grey[200],
|
backgroundColor: theme.palette.grey[200],
|
||||||
margin: '0.5rem 0',
|
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: {
|
column: {
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
@ -1,22 +1,40 @@
|
|||||||
|
import { Delete, Edit } from '@material-ui/icons';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import { useParams } from 'react-router';
|
||||||
|
import { IFeatureViewParams } from '../../../interfaces/params';
|
||||||
import { IConstraint } from '../../../interfaces/strategy';
|
import { IConstraint } from '../../../interfaces/strategy';
|
||||||
import FeatureStrategiesSeparator from '../../feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesSeparator/FeatureStrategiesSeparator';
|
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 StringTruncator from '../StringTruncator/StringTruncator';
|
||||||
import { useStyles } from './Constraint.styles';
|
import { useStyles } from './Constraint.styles';
|
||||||
|
|
||||||
interface IConstraintProps {
|
interface IConstraintProps {
|
||||||
constraint: IConstraint;
|
constraint: IConstraint;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
deleteCallback?: () => void;
|
||||||
|
editCallback?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Constraint = ({ constraint, className, ...rest }: IConstraintProps) => {
|
const Constraint = ({
|
||||||
|
constraint,
|
||||||
|
deleteCallback,
|
||||||
|
editCallback,
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
}: IConstraintProps) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
const { projectId } = useParams<IFeatureViewParams>();
|
||||||
|
|
||||||
const classes = classnames(styles.constraint, {
|
const classes = classnames(styles.constraint, {
|
||||||
[styles.column]: constraint.values.length > 2,
|
[styles.column]: constraint.values.length > 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const editable = !!(deleteCallback && editCallback);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className={classes + ' ' + className} {...rest}>
|
||||||
<div className={classes + ' ' + className} {...rest}>
|
<div className={classes + ' ' + className} {...rest}>
|
||||||
<StringTruncator text={constraint.contextName} maxWidth="125" />
|
<StringTruncator text={constraint.contextName} maxWidth="125" />
|
||||||
<FeatureStrategiesSeparator
|
<FeatureStrategiesSeparator
|
||||||
@ -27,6 +45,32 @@ const Constraint = ({ constraint, className, ...rest }: IConstraintProps) => {
|
|||||||
{constraint.values.join(', ')}
|
{constraint.values.join(', ')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import AccessContext from '../../../contexts/AccessContext';
|
|||||||
|
|
||||||
interface IPermissionIconButtonProps extends OverridableComponent<any> {
|
interface IPermissionIconButtonProps extends OverridableComponent<any> {
|
||||||
permission: string;
|
permission: string;
|
||||||
Icon: React.ElementType;
|
Icon?: React.ElementType;
|
||||||
tooltip: string;
|
tooltip: string;
|
||||||
onClick?: (e: any) => void;
|
onClick?: (e: any) => void;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
|
@ -7,7 +7,8 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
marginBottom: '0.5rem',
|
marginBottom: '0.5rem',
|
||||||
},
|
},
|
||||||
accordionContainer: {
|
accordionContainer: {
|
||||||
width: '80%',
|
width: '100%',
|
||||||
|
paddingRight: '37px',
|
||||||
[theme.breakpoints.down(800)]: {
|
[theme.breakpoints.down(800)]: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
@ -20,7 +21,4 @@ export const useStyles = makeStyles(theme => ({
|
|||||||
marginTop: '0.5rem',
|
marginTop: '0.5rem',
|
||||||
fontSize: theme.fontSizes.smallBody,
|
fontSize: theme.fontSizes.smallBody,
|
||||||
},
|
},
|
||||||
constraintBody: {
|
|
||||||
maxWidth: '350px',
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
@ -12,7 +12,6 @@ import { useContext, useState } from 'react';
|
|||||||
import ConditionallyRender from '../../../../../common/ConditionallyRender';
|
import ConditionallyRender from '../../../../../common/ConditionallyRender';
|
||||||
import useUiConfig from '../../../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from '../../../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { C } from '../../../../../common/flags';
|
import { C } from '../../../../../common/flags';
|
||||||
import { Button } from '@material-ui/core';
|
|
||||||
import { useStyles } from './FeatureStrategyAccordionBody.styles';
|
import { useStyles } from './FeatureStrategyAccordionBody.styles';
|
||||||
import Dialogue from '../../../../../common/Dialogue';
|
import Dialogue from '../../../../../common/Dialogue';
|
||||||
import DefaultStrategy from '../../common/DefaultStrategy/DefaultStrategy';
|
import DefaultStrategy from '../../common/DefaultStrategy/DefaultStrategy';
|
||||||
@ -20,6 +19,9 @@ import { ADD_CONSTRAINT_ID } from '../../../../../../testIds';
|
|||||||
import AccessContext from '../../../../../../contexts/AccessContext';
|
import AccessContext from '../../../../../../contexts/AccessContext';
|
||||||
import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions';
|
import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions';
|
||||||
import Constraint from '../../../../../common/Constraint/Constraint';
|
import Constraint from '../../../../../common/Constraint/Constraint';
|
||||||
|
import PermissionButton from '../../../../../common/PermissionButton/PermissionButton';
|
||||||
|
import { useParams } from 'react-router';
|
||||||
|
import { IFeatureViewParams } from '../../../../../../interfaces/params';
|
||||||
|
|
||||||
interface IFeatureStrategyAccordionBodyProps {
|
interface IFeatureStrategyAccordionBodyProps {
|
||||||
strategy: IFeatureStrategy;
|
strategy: IFeatureStrategy;
|
||||||
@ -40,6 +42,7 @@ const FeatureStrategyAccordionBody: React.FC<IFeatureStrategyAccordionBodyProps>
|
|||||||
setStrategyConstraints,
|
setStrategyConstraints,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
const { projectId } = useParams<IFeatureViewParams>();
|
||||||
const [constraintError, setConstraintError] = useState({});
|
const [constraintError, setConstraintError] = useState({});
|
||||||
const { strategies } = useStrategies();
|
const { strategies } = useStrategies();
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
@ -106,13 +109,25 @@ const FeatureStrategyAccordionBody: React.FC<IFeatureStrategyAccordionBodyProps>
|
|||||||
return (
|
return (
|
||||||
<Constraint
|
<Constraint
|
||||||
constraint={constraint}
|
constraint={constraint}
|
||||||
|
editCallback={() => {
|
||||||
|
setShowConstraints(true);
|
||||||
|
}}
|
||||||
|
deleteCallback={() => {
|
||||||
|
removeConstraint(index);
|
||||||
|
}}
|
||||||
key={`${constraint.contextName}-${index}`}
|
key={`${constraint.contextName}-${index}`}
|
||||||
className={styles.constraintBody}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeConstraint = (index: number) => {
|
||||||
|
const updatedConstraints = [...constraints];
|
||||||
|
updatedConstraints.splice(index, 1);
|
||||||
|
|
||||||
|
updateConstraints(updatedConstraints);
|
||||||
|
};
|
||||||
|
|
||||||
const closeConstraintDialog = () => {
|
const closeConstraintDialog = () => {
|
||||||
setShowConstraints(false);
|
setShowConstraints(false);
|
||||||
const filteredConstraints = constraints.filter(constraint => {
|
const filteredConstraints = constraints.filter(constraint => {
|
||||||
@ -137,18 +152,17 @@ const FeatureStrategyAccordionBody: React.FC<IFeatureStrategyAccordionBodyProps>
|
|||||||
Constraints
|
Constraints
|
||||||
</p>
|
</p>
|
||||||
{renderConstraints()}
|
{renderConstraints()}
|
||||||
<ConditionallyRender
|
|
||||||
condition={hasAccess(UPDATE_FEATURE)}
|
<PermissionButton
|
||||||
show={
|
|
||||||
<Button
|
|
||||||
className={styles.addConstraintBtn}
|
className={styles.addConstraintBtn}
|
||||||
onClick={toggleConstraints}
|
onClick={toggleConstraints}
|
||||||
|
variant={'text'}
|
||||||
data-test={ADD_CONSTRAINT_ID}
|
data-test={ADD_CONSTRAINT_ID}
|
||||||
|
permission={UPDATE_FEATURE}
|
||||||
|
projectId={projectId}
|
||||||
>
|
>
|
||||||
+ Edit constraints
|
+ Add constraints
|
||||||
</Button>
|
</PermissionButton>
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user