diff --git a/frontend/src/app.css b/frontend/src/app.css index d72c55a6ec..a988c73e9f 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -16,6 +16,7 @@ body { .MuiButton-root { border-radius: 3px; text-transform: none; + font-size: 16px; } .skeleton { diff --git a/frontend/src/component/common/Dialogue/Dialogue.jsx b/frontend/src/component/common/Dialogue/Dialogue.jsx index 23c78205c2..89ac22840c 100644 --- a/frontend/src/component/common/Dialogue/Dialogue.jsx +++ b/frontend/src/component/common/Dialogue/Dialogue.jsx @@ -16,8 +16,11 @@ const Dialogue = ({ onClick, onClose, title, + style, primaryButtonText, + disabledPrimaryButton = false, secondaryButtonText, + maxWidth = 'sm', fullWidth = false, }) => { const styles = useStyles(); @@ -28,6 +31,7 @@ const Dialogue = ({ fullWidth={fullWidth} aria-labelledby={'simple-modal-title'} aria-describedby={'simple-modal-description'} + maxWidth={maxWidth} > {title} {primaryButtonText || "Yes, I'm sure"} diff --git a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx index 786de58387..bb5ae6ec57 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx @@ -84,7 +84,7 @@ const FeatureToggleListNew = ({ )} align="left" > - name + Name - type + Type {getEnvironments().map((env: any) => { return ( @@ -109,7 +109,7 @@ const FeatureToggleListNew = ({ > {env.name === ':global:' - ? 'status' + ? 'Status' : env.name} diff --git a/frontend/src/component/feature/strategy/AddStrategy/AddStrategy.jsx b/frontend/src/component/feature/strategy/AddStrategy/AddStrategy.jsx index fcf12ddecb..aad4357377 100644 --- a/frontend/src/component/feature/strategy/AddStrategy/AddStrategy.jsx +++ b/frontend/src/component/feature/strategy/AddStrategy/AddStrategy.jsx @@ -1,19 +1,12 @@ -import React from 'react'; import PropTypes from 'prop-types'; -import { - Button, - Dialog, - DialogContent, - DialogTitle, - DialogActions, - Typography, -} from '@material-ui/core'; +import { Typography } from '@material-ui/core'; import CreateStrategyCard from './AddStrategyCard/AddStrategyCard'; import { useStyles } from './AddStrategy.styles'; import ConditionallyRender from '../../../common/ConditionallyRender'; import { resolveDefaultParamValue } from './utils'; import { getHumanReadbleStrategy } from '../../../../utils/strategy-names'; +import Dialogue from '../../../common/Dialogue'; const AddStrategy = ({ strategies, @@ -98,49 +91,39 @@ const AddStrategy = ({ )); return ( - setShowCreateStrategy(false)} + secondaryButtonText="Cancel" maxWidth="md" + fullWidth > - Add a new strategy + + Built in strategies + +
+ {renderBuiltInStrategies()} +
- - - Built in strategies - -
- {renderBuiltInStrategies()} -
- - 0} - show={ - <> - - Custom strategies - -
- {renderCustomStrategies()} -
- - } - /> -
- - - - -
+ 0} + show={ + <> + + Custom strategies + +
+ {renderCustomStrategies()} +
+ + } + /> + ); }; diff --git a/frontend/src/component/feature/strategy/EditStrategyModal/EditStrategyModal.jsx b/frontend/src/component/feature/strategy/EditStrategyModal/EditStrategyModal.jsx index bf90bd4d7e..d90692eb11 100644 --- a/frontend/src/component/feature/strategy/EditStrategyModal/EditStrategyModal.jsx +++ b/frontend/src/component/feature/strategy/EditStrategyModal/EditStrategyModal.jsx @@ -1,12 +1,5 @@ -import React from 'react'; +import { useState } from 'react'; import PropTypes from 'prop-types'; -import { - Button, - Dialog, - DialogContent, - DialogTitle, - DialogActions, -} from '@material-ui/core'; import FlexibleStrategy from './FlexibleStrategy'; import DefaultStrategy from './default-strategy'; @@ -15,6 +8,7 @@ import GeneralStrategy from './general-strategy'; import StrategyConstraints from '../StrategyConstraint/StrategyConstraintInput'; import { getHumanReadbleStrategyName } from '../../../../utils/strategy-names'; +import Dialogue from '../../../common/Dialogue'; const EditStrategyModal = ({ onCancel, @@ -24,6 +18,8 @@ const EditStrategyModal = ({ strategyDefinition, context, }) => { + const [constraintError, setConstraintError] = useState({}); + const updateParameters = parameters => { const updatedStrategy = { ...strategy, parameters }; updateStrategy(updatedStrategy); @@ -57,47 +53,66 @@ const EditStrategyModal = ({ const { parameters } = strategy; + const disabledPrimaryButton = Object.keys(constraintError).some(key => { + return constraintError[key]; + }); + + const save = () => { + const { constraints } = strategy; + let valid = true; + + constraints.forEach((constraint, index) => { + const { values } = constraint; + + if (values.length === 0) { + setConstraintError(prev => ({ + ...prev, + [`${constraint.contextName}-${index}`]: + 'You need to specify at least one value', + })); + valid = false; + } + }); + + if (valid) { + saveStrategy(); + } + }; + return ( - - - Configure {getHumanReadbleStrategyName(strategy.name)} strategy - - -
- -
- -
-
- + -
- - - - -
+ + +
+
+ + ); }; diff --git a/frontend/src/component/feature/strategy/StrategyConstraint/StrategyConstraintInput/StrategyConstraintInput.jsx b/frontend/src/component/feature/strategy/StrategyConstraint/StrategyConstraintInput/StrategyConstraintInput.jsx index f977349557..005dbfc7a5 100644 --- a/frontend/src/component/feature/strategy/StrategyConstraint/StrategyConstraintInput/StrategyConstraintInput.jsx +++ b/frontend/src/component/feature/strategy/StrategyConstraint/StrategyConstraintInput/StrategyConstraintInput.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Tooltip, Typography } from '@material-ui/core'; +import { Button, Tooltip, Typography } from '@material-ui/core'; import { Info } from '@material-ui/icons'; import StrategyConstraintInputField from '../StrategyConstraintInputField'; @@ -12,6 +12,8 @@ const StrategyConstraintInput = ({ contextNames, contextFields, enabled, + constraintError, + setConstraintError, }) => { const commonStyles = useCommonStyles(); const addConstraint = evt => { @@ -71,18 +73,21 @@ const StrategyConstraintInput = ({ contextFields={contextFields} updateConstraint={updateConstraint(index)} removeConstraint={removeConstraint(index)} + constraintError={constraintError} + setConstraintError={setConstraintError} /> ))} - Add constraint - + ); diff --git a/frontend/src/component/feature/strategy/StrategyConstraint/StrategyConstraintInputField/StrategyConstraintInputField.jsx b/frontend/src/component/feature/strategy/StrategyConstraint/StrategyConstraintInputField/StrategyConstraintInputField.jsx index 1671b20409..c58e3ff678 100644 --- a/frontend/src/component/feature/strategy/StrategyConstraint/StrategyConstraintInputField/StrategyConstraintInputField.jsx +++ b/frontend/src/component/feature/strategy/StrategyConstraint/StrategyConstraintInputField/StrategyConstraintInputField.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useEffect } from 'react'; import PropTypes from 'prop-types'; import { IconButton, TextField } from '@material-ui/core'; import { Autocomplete } from '@material-ui/lab'; @@ -20,27 +20,47 @@ const StrategyConstraintInputField = ({ updateConstraint, removeConstraint, contextFields, + id, + constraintError, + setConstraintError, }) => { - const [error, setError] = useState(); - const commonStyles = useCommonStyles(); - const styles = useStyles(); - const onBlur = evt => { - evt.preventDefault(); + useEffect(() => { + return () => { + resetError(); + }; + /*eslint-disable-next-line */ + }, []); + + const checkError = () => { const values = constraint.values; const filtered = values.filter(v => v).map(v => v.trim()); if (filtered.length !== values.length) { updateConstraint(filtered, 'values'); } if (filtered.length === 0) { - setError('You need to specify at least one value'); + setConstraintError(prev => ({ + ...prev, + [id]: 'You need to specify at least one value', + })); } else { - setError(undefined); + resetError(); } }; + const resetError = () => + setConstraintError(prev => ({ ...prev, [id]: undefined })); + + const commonStyles = useCommonStyles(); + const styles = useStyles(); + const onBlur = evt => { + evt.preventDefault(); + checkError(); + }; + const handleChangeValue = selectedOptions => { const values = selectedOptions ? selectedOptions.map(o => o.value) : []; updateConstraint(values, 'values'); + checkError(); }; const constraintContextNames = contextFields.map(f => ({ @@ -59,9 +79,11 @@ const StrategyConstraintInputField = ({ : undefined; const values = constraint.values.map(v => ({ value: v, label: v })); + const error = constraintError[id]; + return ( - + - + - + option.label} - getOptionSelected={(option, value) => - option.value === value.value - } - filterSelectedOptions - filterOptions={options => - options.filter( - o => !values.some(v => v.value === o.value) - ) - } - onChange={(evt, values) => - handleChangeValue(values) - } - renderInput={params => ( - - )} - /> +
+ option.label} + onBlur={onBlur} + onFocus={() => resetError()} + getOptionSelected={(option, value) => + option.value === value.value + } + filterSelectedOptions + filterOptions={options => + options.filter( + o => + !values.some( + v => v.value === o.value + ) + ) + } + onChange={(evt, values) => + handleChangeValue(values) + } + renderInput={params => ( + + )} + /> +
} elseShow={ - - updateConstraint(values, 'values') - } - /> +
+ + updateConstraint(values, 'values') + } + helperText={error} + FormHelperTextProps={{ + classes: { + root: styles.helperText, + }, + }} + /> +
} /> - + diff --git a/frontend/src/component/feature/strategy/StrategyConstraint/StrategyConstraintInputField/StrategyConstraintInputField.styles.js b/frontend/src/component/feature/strategy/StrategyConstraint/StrategyConstraintInputField/StrategyConstraintInputField.styles.js index 6bbfae29da..8a604270ff 100644 --- a/frontend/src/component/feature/strategy/StrategyConstraint/StrategyConstraintInputField/StrategyConstraintInputField.styles.js +++ b/frontend/src/component/feature/strategy/StrategyConstraint/StrategyConstraintInputField/StrategyConstraintInputField.styles.js @@ -7,4 +7,21 @@ export const useStyles = makeStyles({ operator: { minWidth: '105px', }, + inputContainer: { + position: 'relative', + }, + inputError: { + position: 'absolute', + fontSize: '0.9rem', + color: 'red', + top: '10px', + left: '12px', + }, + tableCell: { + paddingBottom: '1.25rem', + }, + helperText: { + position: 'absolute', + top: '35px', + }, }); diff --git a/frontend/src/component/feature/variant/AddVariant/AddVariant.jsx b/frontend/src/component/feature/variant/AddVariant/AddVariant.jsx index b857026722..90d210aff8 100644 --- a/frontend/src/component/feature/variant/AddVariant/AddVariant.jsx +++ b/frontend/src/component/feature/variant/AddVariant/AddVariant.jsx @@ -187,6 +187,8 @@ const AddVariant = ({ primaryButtonText="Save" secondaryButtonText="Cancel" title={title} + fullWidth + maxWidth="md" >

{error.general}

@@ -195,6 +197,7 @@ const AddVariant = ({ name="name" placeholder="" className={commonStyles.fullWidth} + style={{ maxWidth: '350px' }} helperText={error.name} value={data.name || ''} error={Boolean(error.name)} @@ -250,11 +253,17 @@ const AddVariant = ({ title="Passed to the variant object. Can be anything (json, value, csv)" > - +

- + - + 0} show={ -

+

Overrides @@ -301,7 +311,13 @@ const AddVariant = ({ updateOverrideValues={updateOverrideValues} updateValues={updateOverrideValues} /> - {' '} + {' '}

); diff --git a/frontend/src/component/feature/variant/AddVariant/OverrideConfig/OverrideConfig.jsx b/frontend/src/component/feature/variant/AddVariant/OverrideConfig/OverrideConfig.jsx index c72eb9677f..a02790b3b5 100644 --- a/frontend/src/component/feature/variant/AddVariant/OverrideConfig/OverrideConfig.jsx +++ b/frontend/src/component/feature/variant/AddVariant/OverrideConfig/OverrideConfig.jsx @@ -41,7 +41,13 @@ const OverrideConfig = ({ return ( - + - + 0} show={ @@ -68,6 +74,7 @@ const OverrideConfig = ({ getOptionLabel={option => option} defaultValue={o.values} value={o.values} + style={{ width: '100%' }} filterSelectedOptions size="small" renderInput={params => ( @@ -75,6 +82,7 @@ const OverrideConfig = ({ {...params} variant="outlined" label="Legal values" + style={{ width: '100%' }} /> )} /> diff --git a/frontend/src/component/menu/Footer/Footer.jsx b/frontend/src/component/menu/Footer/Footer.jsx index f643d6721f..197d54e803 100644 --- a/frontend/src/component/menu/Footer/Footer.jsx +++ b/frontend/src/component/menu/Footer/Footer.jsx @@ -16,255 +16,253 @@ export const Footer = () => { - - - -
-

Server SDKs

- - - - Node.js - - } - /> - - - - Java - - } - /> - - - - Go - - } - /> - {' '} - - - Ruby - - } - /> - {' '} - - - Python - - } - /> - - - - .NET - - } - /> - - - - PHP - - } - /> - - - - All SDKs - - } - /> - - -
-
- -
-

Frontend SDKs

- - - - Unleash Proxy - - } - /> - - - - JavaScript SDK - - } - /> - - - - React SDK - - } - /> - - - - iOS SDK - - } - /> - - - - Android SDK - - } - /> - - -
-
- -
-

About

- - - - getunleash.io - - } - /> - - - - Twitter - - } - /> - - - - LinkedIn - - } - /> - - - - GitHub - - } - /> - - -
+ + + +
+

Server SDKs

+ + + + Node.js + + } + /> + + + + Java + + } + /> + + + + Go + + } + /> + {' '} + + + Ruby + + } + /> + {' '} + + + Python + + } + /> + + + + .NET + + } + /> + + + + PHP + + } + /> + + + + All SDKs + + } + /> + + +
+
+ +
+

Frontend SDKs

+ + + + Unleash Proxy + + } + /> + + + + JavaScript SDK + + } + /> + + + + React SDK + + } + /> + + + + iOS SDK + + } + /> + + + + Android SDK + + } + /> + + +
+
+ +
+

About

+ + + + getunleash.io + + } + /> + + + + Twitter + + } + /> + + + + LinkedIn + + } + /> + + + + GitHub + + } + /> + + +
+
+
-
-
- - ); }; diff --git a/frontend/src/component/menu/__tests__/__snapshots__/footer-test.jsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/footer-test.jsx.snap index 09ae63ad5c..ffd6e7d91c 100644 --- a/frontend/src/component/menu/__tests__/__snapshots__/footer-test.jsx.snap +++ b/frontend/src/component/menu/__tests__/__snapshots__/footer-test.jsx.snap @@ -199,7 +199,6 @@ exports[`should render DrawerMenu 1`] = `