From b85b3261040b1f91d2be05550f9a9e62488ad296 Mon Sep 17 00:00:00 2001 From: sighphyre <liquidwicked64@gmail.com> Date: Fri, 26 Nov 2021 17:07:05 +0200 Subject: [PATCH] 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> --- .../feature-toggle/feature.spec.js | 4 +- frontend/package.json | 1 + .../common/Constraint/Constraint.styles.ts | 14 ++++- .../common/Constraint/Constraint.tsx | 60 ++++++++++++++++--- .../PermissionIconButton.tsx | 2 +- .../FeatureStrategyAccordionBody.styles.ts | 6 +- .../FeatureStrategyAccordionBody.tsx | 42 ++++++++----- 7 files changed, 99 insertions(+), 30 deletions(-) diff --git a/frontend/cypress/integration/feature-toggle/feature.spec.js b/frontend/cypress/integration/feature-toggle/feature.spec.js index 404951534c..ea4d779eb6 100644 --- a/frontend/cypress/integration/feature-toggle/feature.spec.js +++ b/frontend/cypress/integration/feature-toggle/feature.spec.js @@ -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'); diff --git a/frontend/package.json b/frontend/package.json index c88647cee9..ecaf782a48 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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": { diff --git a/frontend/src/component/common/Constraint/Constraint.styles.ts b/frontend/src/component/common/Constraint/Constraint.styles.ts index d9b3834573..2541965ff9 100644 --- a/frontend/src/component/common/Constraint/Constraint.styles.ts +++ b/frontend/src/component/common/Constraint/Constraint.styles.ts @@ -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', diff --git a/frontend/src/component/common/Constraint/Constraint.tsx b/frontend/src/component/common/Constraint/Constraint.tsx index de1c96c1cc..8b960fac12 100644 --- a/frontend/src/component/common/Constraint/Constraint.tsx +++ b/frontend/src/component/common/Constraint/Constraint.tsx @@ -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> ); }; diff --git a/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx b/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx index ac743785ef..3c02b8b654 100644 --- a/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx +++ b/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx @@ -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; diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.styles.ts index 0dc423ddbf..399f20e3fa 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.styles.ts @@ -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', - }, })); diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx index 30266bfbc4..dc47742308 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx @@ -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> </> } />