diff --git a/frontend/src/component/common/Constraint/Constraint.styles.ts b/frontend/src/component/common/Constraint/Constraint.styles.ts deleted file mode 100644 index 879dc64e67..0000000000 --- a/frontend/src/component/common/Constraint/Constraint.styles.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { makeStyles } from '@material-ui/core/styles'; - -export const useStyles = makeStyles(theme => ({ - constraintHeader: { - fontWeight: 'bold', - fontSize: theme.fontSizes.smallBody, - }, - constraint: { - width: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - padding: '0.1rem 0.5rem', - 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', - }, - values: { - marginLeft: '1.5rem', - whiteSpace: 'pre-wrap', - }, -})); diff --git a/frontend/src/component/common/Constraint/Constraint.tsx b/frontend/src/component/common/Constraint/Constraint.tsx deleted file mode 100644 index 3703b28a2a..0000000000 --- a/frontend/src/component/common/Constraint/Constraint.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { Delete, Edit } from '@material-ui/icons'; -import classnames from 'classnames'; -import { IN, NOT_IN } from 'constants/operators'; -import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; -import { useParams } from 'react-router'; -import { IFeatureViewParams } from 'interfaces/params'; -import { IConstraint } from 'interfaces/strategy'; -import { StrategySeparator } from '../StrategySeparator/StrategySeparator'; -import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions'; -import ConditionallyRender from '../ConditionallyRender'; -import PermissionIconButton from '../PermissionIconButton/PermissionIconButton'; -import StringTruncator from '../StringTruncator/StringTruncator'; -import { useStyles } from './Constraint.styles'; -import { useLocationSettings } from 'hooks/useLocationSettings'; -import { formatConstraintValuesOrValue } from 'component/common/Constraint/formatConstraintValue'; - -interface IConstraintProps { - constraint: IConstraint; - className?: string; - deleteCallback?: () => void; - editCallback?: () => void; -} - -const Constraint = ({ - constraint, - deleteCallback, - editCallback, - className, - ...rest -}: IConstraintProps) => { - // CHANGEME - Feat: Constraint Operators - const { uiConfig } = useUiConfig(); - const styles = useStyles(); - const { locationSettings } = useLocationSettings(); - const { projectId } = useParams(); - - const classes = classnames(styles.constraint, { - [styles.column]: - Array.isArray(constraint.values) && constraint.values.length > 2, - }); - - const editable = !!(deleteCallback && editCallback); - // CHANGEME - Feat: Constraint Operators - // Disable the edit button for constraints that are using new operators if - // the new operators are not enabled - const operatorIsNew = - constraint.operator !== IN && constraint.operator !== NOT_IN; - const disabledEdit = !uiConfig.flags.CO && operatorIsNew; - - return ( -
-
- - - - {formatConstraintValuesOrValue( - constraint, - locationSettings - )} - -
- - - - - - - - - -
- } - /> - - ); -}; - -export default Constraint; diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.styles.ts b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.styles.ts index ae7694ac50..1beb9b449c 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.styles.ts +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordion.styles.ts @@ -20,40 +20,21 @@ export const useStyles = makeStyles(theme => ({ width: '26px', height: '26px', }, - accordionRoot: { margin: 0, boxShadow: 'none' }, - negated: { - position: 'absolute', - color: '#fff', - backgroundColor: theme.palette.primary.light, - padding: '0.1rem 0.2rem', - fontSize: '0.7rem', - fontWeight: 'bold', - top: '-15px', - left: '42px', - borderRadius: '3px', - }, accordion: { border: `1px solid ${theme.palette.grey[300]}`, borderRadius: '5px', backgroundColor: '#fff', + boxShadow: 'none', margin: 0, - - ['&:before']: { - height: 0, + }, + accordionRoot: { + '&:before': { + opacity: '0 !important', }, }, accordionEdit: { backgroundColor: '#F6F6FA', }, - operator: { - border: `1px solid ${theme.palette.secondary.main}`, - padding: '0.25rem 1rem', - color: theme.palette.secondary.main, - textTransform: 'uppercase', - borderRadius: '5px', - margin: '0rem 2rem', - fontSize: theme.fontSizes.smallBody, - }, headerMetaInfo: { display: 'flex', alignItems: 'center', @@ -80,6 +61,14 @@ export const useStyles = makeStyles(theme => ({ headerValuesExpand: { fontSize: theme.fontSizes.smallBody, }, + headerConstraintContainer: { + minWidth: '220px', + position: 'relative', + paddingRight: '1rem', + [theme.breakpoints.down(650)]: { + paddingRight: 0, + }, + }, headerViewValuesContainer: { [theme.breakpoints.down(990)]: { display: 'none', @@ -132,6 +121,7 @@ export const useStyles = makeStyles(theme => ({ }, headerActions: { marginLeft: 'auto', + whiteSpace: 'nowrap', [theme.breakpoints.down(660)]: { marginLeft: '0', marginTop: '0.5rem', @@ -152,7 +142,7 @@ export const useStyles = makeStyles(theme => ({ padding: '0.25rem 1rem', height: '85px', [theme.breakpoints.down(770)]: { - height: '175px', + height: '200px', }, }, settingsParagraph: { diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx index ae3fc66b5d..bdb454f53c 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintAccordionEditBody.tsx @@ -97,7 +97,7 @@ const InvertedOperator = ({ color="primary" /> } - label={'negated'} + label="Negated" /> ); diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditHeader/ConstraintAccordionEditHeader.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditHeader/ConstraintAccordionEditHeader.tsx index 8d759eee61..d35476efda 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditHeader/ConstraintAccordionEditHeader.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditHeader/ConstraintAccordionEditHeader.tsx @@ -6,17 +6,17 @@ import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon'; import { Help } from '@material-ui/icons'; import ConditionallyRender from 'component/common/ConditionallyRender'; -import { - allOperators, - dateOperators, - DATE_AFTER, - IN, -} from 'constants/operators'; +import { dateOperators, DATE_AFTER, IN } from 'constants/operators'; import { SAVE } from '../ConstraintAccordionEdit'; import { resolveText } from './helpers'; import { oneOf } from 'utils/oneOf'; -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; import { Operator } from 'constants/operators'; +import { ConstraintOperatorSelect } from 'component/common/ConstraintAccordion/ConstraintOperatorSelect/ConstraintOperatorSelect'; +import { + operatorsForContext, + CURRENT_TIME_CONTEXT_FIELD, +} from 'utils/operatorsForContext'; interface IConstraintAccordionViewHeader { localConstraint: IConstraint; @@ -27,12 +27,6 @@ interface IConstraintAccordionViewHeader { compact: boolean; } -const constraintOperators = allOperators.map(operator => { - return { key: operator, label: operator }; -}); - -export const CURRENT_TIME_CONTEXT_FIELD = 'currentTime'; - export const ConstraintAccordionEditHeader = ({ compact, localConstraint, @@ -43,6 +37,7 @@ export const ConstraintAccordionEditHeader = ({ }: IConstraintAccordionViewHeader) => { const styles = useStyles(); const { context } = useUnleashContext(); + const { contextName, operator } = localConstraint; /* We need a special case to handle the currenTime context field. Since this field will be the only one to allow DATE_BEFORE and DATE_AFTER operators @@ -51,8 +46,8 @@ export const ConstraintAccordionEditHeader = ({ data). */ useEffect(() => { if ( - localConstraint.contextName === CURRENT_TIME_CONTEXT_FIELD && - !oneOf(dateOperators, localConstraint.operator) + contextName === CURRENT_TIME_CONTEXT_FIELD && + !oneOf(dateOperators, operator) ) { setLocalConstraint(prev => ({ ...prev, @@ -60,48 +55,22 @@ export const ConstraintAccordionEditHeader = ({ value: new Date().toISOString(), })); } else if ( - localConstraint.contextName !== CURRENT_TIME_CONTEXT_FIELD && - oneOf(dateOperators, localConstraint.operator) + contextName !== CURRENT_TIME_CONTEXT_FIELD && + oneOf(dateOperators, operator) ) { setOperator(IN); } - }, [ - localConstraint.contextName, - setOperator, - localConstraint.operator, - setLocalConstraint, - ]); + }, [contextName, setOperator, operator, setLocalConstraint]); + + if (!context) { + return null; + } - if (!context) return null; const constraintNameOptions = context.map(context => { return { key: context.name, label: context.name }; }); - const filteredOperators = constraintOperators.filter(operator => { - if ( - oneOf(dateOperators, operator.label) && - localConstraint.contextName !== CURRENT_TIME_CONTEXT_FIELD - ) { - return false; - } - - if ( - !oneOf(dateOperators, operator.label) && - localConstraint.contextName === CURRENT_TIME_CONTEXT_FIELD - ) { - return false; - } - - return true; - }); - - const onChange = ( - event: React.ChangeEvent<{ - name?: string; - value: unknown; - }> - ) => { - const operator = event.target.value as Operator; + const onOperatorChange = (operator: Operator) => { if (oneOf(dateOperators, operator)) { setLocalConstraint(prev => ({ ...prev, @@ -124,42 +93,34 @@ export const ConstraintAccordionEditHeader = ({ label="Context Field" autoFocus options={constraintNameOptions} - value={localConstraint.contextName || ''} + value={contextName || ''} onChange={e => setContextName(String(e.target.value))} className={styles.headerSelect} />
- +
+ +
- - {resolveText( - localConstraint.operator, - localConstraint.contextName - )} + {resolveText(operator, contextName)}

} /> - Updating...

} elseShow={

Editing

} /> - { const operator = operatorsForContext(contextName)[0]; diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx index 35cdcdbab3..da4a50b9f3 100644 --- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx @@ -41,10 +41,7 @@ export const ConstraintAccordionView = ({ return ( - -
- NOT
} - /> -

{constraint.operator}

+
+

- {constraint?.values?.length} values + {constraint?.values?.length}{' '} + {constraint?.values?.length === 1 + ? 'value' + : 'values'}

Expand to view diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.styles.ts b/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.styles.ts new file mode 100644 index 0000000000..80b60a30ec --- /dev/null +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.styles.ts @@ -0,0 +1,30 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles(theme => ({ + container: { + padding: '0.5rem 0.75rem', + borderRadius: theme.borders.radius.main, + backgroundColor: theme.palette.grey[200], + lineHeight: 1.25, + }, + name: { + fontSize: theme.fontSizes.smallBody, + }, + text: { + fontSize: theme.fontSizes.smallerBody, + color: theme.palette.grey[700], + }, + not: { + display: 'block', + margin: '-1rem 0 0.25rem 0', + height: '1rem', + '& > span': { + display: 'inline-block', + padding: '0 0.25rem', + borderRadius: theme.borders.radius.main, + fontSize: theme.fontSizes.smallerBody, + backgroundColor: theme.palette.primary.light, + color: 'white', + }, + }, +})); diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.tsx new file mode 100644 index 0000000000..d76030518e --- /dev/null +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.tsx @@ -0,0 +1,30 @@ +import { IConstraint } from 'interfaces/strategy'; +import { formatOperatorDescription } from 'component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription'; +import { useStyles } from 'component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.styles'; + +interface IConstraintOperatorProps { + constraint: IConstraint; +} + +export const ConstraintOperator = ({ + constraint, +}: IConstraintOperatorProps) => { + const styles = useStyles(); + + const operatorName = constraint.operator; + const operatorText = formatOperatorDescription(constraint.operator); + + const notLabel = constraint.inverted && ( +

+ NOT +
+ ); + + return ( +
+ {notLabel} +
{operatorName}
+
{operatorText}
+
+ ); +}; diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription.ts b/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription.ts new file mode 100644 index 0000000000..ef4a76118e --- /dev/null +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription.ts @@ -0,0 +1,23 @@ +import { Operator } from 'constants/operators'; + +export const formatOperatorDescription = (operator: Operator): string => { + return constraintOperatorDescriptions[operator]; +}; + +const constraintOperatorDescriptions = { + IN: 'is one of', + NOT_IN: 'is not one of', + STR_CONTAINS: 'is a string that contains', + STR_STARTS_WITH: 'is a string that starts with', + STR_ENDS_WITH: 'is a string that ends with', + NUM_EQ: 'is a number equal to', + NUM_GT: 'is a number greater than', + NUM_GTE: 'is a number greater than or equal to', + NUM_LT: 'is a number less than', + NUM_LTE: 'is a number less than or equal to', + DATE_BEFORE: 'is a date before', + DATE_AFTER: 'is a date after', + SEMVER_EQ: 'is a SemVer equal to', + SEMVER_GT: 'is a SemVer greater than', + SEMVER_LT: 'is a SemVer less than', +}; diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintOperatorSelect/ConstraintOperatorSelect.styles.ts b/frontend/src/component/common/ConstraintAccordion/ConstraintOperatorSelect/ConstraintOperatorSelect.styles.ts new file mode 100644 index 0000000000..4de9fb2a64 --- /dev/null +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintOperatorSelect/ConstraintOperatorSelect.styles.ts @@ -0,0 +1,37 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles(theme => ({ + valueContainer: { + lineHeight: 1.1, + marginTop: -5, + marginBottom: -10, + }, + optionContainer: { + lineHeight: 1.2, + }, + label: { + fontSize: theme.fontSizes.smallBody, + }, + description: { + fontSize: theme.fontSizes.smallerBody, + color: theme.palette.grey[700], + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + }, + separator: { + position: 'relative', + overflow: 'visible', + marginTop: '1rem', + '&:before': { + content: '""', + display: 'block', + position: 'absolute', + top: '-0.5rem', + left: 0, + right: 0, + borderTop: '1px solid', + borderTopColor: theme.palette.grey[300], + }, + }, +})); diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintOperatorSelect/ConstraintOperatorSelect.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintOperatorSelect/ConstraintOperatorSelect.tsx new file mode 100644 index 0000000000..d922148cdf --- /dev/null +++ b/frontend/src/component/common/ConstraintAccordion/ConstraintOperatorSelect/ConstraintOperatorSelect.tsx @@ -0,0 +1,91 @@ +import { Select, MenuItem, FormControl, InputLabel } from '@material-ui/core'; +import { + Operator, + stringOperators, + semVerOperators, + dateOperators, + numOperators, +} from 'constants/operators'; +import React, { useState, ChangeEvent } from 'react'; +import { formatOperatorDescription } from 'component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription'; +import { useStyles } from 'component/common/ConstraintAccordion/ConstraintOperatorSelect/ConstraintOperatorSelect.styles'; +import classNames from 'classnames'; + +interface IConstraintOperatorSelectProps { + options: Operator[]; + value: Operator; + onChange: (value: Operator) => void; +} + +export const ConstraintOperatorSelect = ({ + options, + value, + onChange, +}: IConstraintOperatorSelectProps) => { + const styles = useStyles(); + const [open, setOpen] = useState(false); + + const onSelectChange = ( + event: ChangeEvent<{ name?: string; value: unknown }> + ) => { + onChange(event.target.value as Operator); + }; + + const renderValue = () => { + return ( +
+
{value}
+
+ {formatOperatorDescription(value)} +
+
+ ); + }; + + return ( + + Operator + + + ); +}; + +const needSeparatorAbove = (operator: Operator): boolean => { + const groups = [ + stringOperators, + numOperators, + dateOperators, + semVerOperators, + ]; + + return groups.some(group => { + return group[0] === operator; + }); +}; diff --git a/frontend/src/component/common/StrategySeparator/StrategySeparator.tsx b/frontend/src/component/common/StrategySeparator/StrategySeparator.tsx index 8839c9f9d8..6a44068fcd 100644 --- a/frontend/src/component/common/StrategySeparator/StrategySeparator.tsx +++ b/frontend/src/component/common/StrategySeparator/StrategySeparator.tsx @@ -2,14 +2,11 @@ import { useTheme } from '@material-ui/core'; interface IStrategySeparatorProps { text: string; - maxWidth?: string; } -export const StrategySeparator = ({ - text, - maxWidth = '50px', -}: IStrategySeparatorProps) => { +export const StrategySeparator = ({ text }: IStrategySeparatorProps) => { const theme = useTheme(); + return (
diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/FeatureStrategyConstraints.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/FeatureStrategyConstraints.tsx index fff9a899c3..15dd1c0cc9 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/FeatureStrategyConstraints.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/FeatureStrategyConstraints.tsx @@ -1,15 +1,6 @@ import { IConstraint, IFeatureStrategy } from 'interfaces/strategy'; -import Constraint from 'component/common/Constraint/Constraint'; -import Dialogue from 'component/common/Dialogue/Dialogue'; -import React, { useState } from 'react'; -import StrategyConstraints from 'component/feature/StrategyConstraints/StrategyConstraints'; -import { List, ListItem } from '@material-ui/core'; -import produce from 'immer'; -import { - CREATE_FEATURE_STRATEGY, - UPDATE_FEATURE_STRATEGY, -} from 'component/providers/AccessProvider/permissions'; -import PermissionButton from 'component/common/PermissionButton/PermissionButton'; +import React, { useMemo } from 'react'; +import { ConstraintAccordionList } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList'; interface IFeatureStrategyConstraintsProps { projectId: string; @@ -26,102 +17,24 @@ export const FeatureStrategyConstraints = ({ strategy, setStrategy, }: IFeatureStrategyConstraintsProps) => { - const [showConstraintsDialog, setShowConstraintsDialog] = useState(false); + const constraints = useMemo(() => { + return strategy.constraints ?? []; + }, [strategy]); - const [constraintErrors, setConstraintErrors] = useState< - Record - >({}); - - const updateConstraints = (constraints: IConstraint[]) => { - setStrategy(prev => ({ ...prev, constraints })); - }; - - const removeConstraint = (index: number) => { - setStrategy( - produce(draft => { - draft.constraints?.splice(index, 1); - }) - ); - }; - - const onConstraintsDialogSave = () => { - const errors = findConstraintErrors(strategy.constraints); - if (Object.keys(errors).length > 0) { - setConstraintErrors(errors); - } else { - setShowConstraintsDialog(false); - } - }; - - const onConstraintsDialogClose = () => { - setStrategy( - produce(draft => { - draft.constraints = removeEmptyConstraints(draft.constraints); - }) - ); - setShowConstraintsDialog(false); + const setConstraints = (value: React.SetStateAction) => { + setStrategy(prev => ({ + ...prev, + constraints: value instanceof Function ? value(constraints) : value, + })); }; return ( -
- - {strategy.constraints?.map((constraint, index) => ( - - setShowConstraintsDialog(true)} - deleteCallback={removeConstraint.bind(null, index)} - /> - - ))} - - - - - setShowConstraintsDialog(true)} - variant="text" - permission={[UPDATE_FEATURE_STRATEGY, CREATE_FEATURE_STRATEGY]} - environmentId={environmentId} - projectId={projectId} - > - Add constraints - -
+ ); }; - -const findConstraintErrors = ( - constraints: IConstraint[] = [] -): Record => { - const entries = constraints - .filter(isEmptyConstraint) - .map((constraint, index) => `${constraint.contextName}-${index}`) - .map(id => [id, 'You need to specify at least one value']); - - return Object.fromEntries(entries); -}; - -const removeEmptyConstraints = ( - constraints: IConstraint[] = [] -): IConstraint[] => { - return constraints.filter(constraint => !isEmptyConstraint(constraint)); -}; - -const isEmptyConstraint = (constraint: IConstraint): boolean => { - return !constraint.values || constraint.values.length === 0; -}; diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/FeatureStrategyConstraintsCO.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/FeatureStrategyConstraintsCO.tsx deleted file mode 100644 index aee63c1c10..0000000000 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/FeatureStrategyConstraintsCO.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { IConstraint, IFeatureStrategy } from 'interfaces/strategy'; -import React, { useMemo } from 'react'; -import { ConstraintAccordionList } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList'; - -interface IFeatureStrategyConstraintsProps { - projectId: string; - environmentId: string; - strategy: Partial; - setStrategy: React.Dispatch< - React.SetStateAction> - >; -} - -export const FeatureStrategyConstraintsCO = ({ - projectId, - environmentId, - strategy, - setStrategy, -}: IFeatureStrategyConstraintsProps) => { - const constraints = useMemo(() => { - return strategy.constraints ?? []; - }, [strategy]); - - const setConstraints = (value: React.SetStateAction) => { - setStrategy(prev => ({ - ...prev, - constraints: value instanceof Function ? value(constraints) : value, - })); - }; - - return ( - - ); -}; diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx index 58ca22d97f..c3e243fb6c 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm.tsx @@ -18,7 +18,6 @@ import { STRATEGY_FORM_SUBMIT_ID } from 'utils/testIds'; import { useConstraintsValidation } from 'hooks/api/getters/useConstraintsValidation/useConstraintsValidation'; import AccessContext from 'contexts/AccessContext'; import PermissionButton from 'component/common/PermissionButton/PermissionButton'; -import { FeatureStrategyConstraintsCO } from 'component/feature/FeatureStrategy/FeatureStrategyConstraints/FeatureStrategyConstraintsCO'; import { FeatureStrategySegment } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment'; import { ISegment } from 'interfaces/segment'; @@ -77,19 +76,11 @@ export const FeatureStrategyForm = ({ throw uiConfigError; } - // Wait for uiConfig to load for the correct uiConfig.flags.CO value. + // Wait for uiConfig to load to get the correct flags. if (uiConfigLoading) { return null; } - // TODO(olav): Remove FeatureStrategyConstraints when CO is out. - const FeatureStrategyConstraintsImplementation = uiConfig.flags.CO - ? FeatureStrategyConstraintsCO - : FeatureStrategyConstraints; - const disableSubmitButtonFromConstraints = uiConfig.flags.CO - ? !hasValidConstraints - : false; - return (
@@ -109,9 +100,9 @@ export const FeatureStrategyForm = ({ } /> } /> Save strategy diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts index 694dcb8b9d..15dc63e2e7 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts @@ -61,6 +61,7 @@ export const useStyles = makeStyles(theme => ({ }, separatorText: { fontSize: theme.fontSizes.smallBody, + textAlign: 'center', padding: '0 1rem', }, rightWing: { diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategies.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategies.tsx index 117f706138..fff5abc801 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategies.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategies.tsx @@ -10,28 +10,16 @@ const FeatureOverviewEnvironmentStrategies = ({ strategies, environmentName, }: FeatureOverviewEnvironmentStrategiesProps) => { - const renderStrategies = () => { - return strategies.map(strategy => { - return ( + return ( + <> + {strategies.map(strategy => ( - ); - }); - }; - - return ( -
- {renderStrategies()} -
+ ))} + ); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.styles.ts b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.styles.ts index 21010f3a8f..bb20aa0a33 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.styles.ts +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.styles.ts @@ -2,10 +2,11 @@ import { makeStyles } from '@material-ui/core/styles'; export const useStyles = makeStyles(theme => ({ container: { - borderRadius: '12.5px', + borderRadius: theme.borders.radius.main, border: `1px solid ${theme.palette.grey[300]}`, - width: '400px', - margin: '0.3rem', + '& + &': { + marginTop: '1rem', + }, }, header: { padding: '0.5rem', @@ -22,9 +23,8 @@ export const useStyles = makeStyles(theme => ({ }, body: { padding: '1rem', - display: 'flex', - justifyContent: 'center', - flexDirection: 'column', - alignItems: 'center', + display: 'grid', + gap: '1rem', + justifyItems: 'center', }, })); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.tsx index 87901d067d..66fcdda13f 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.tsx @@ -65,7 +65,6 @@ const FeatureOverviewEnvironmentStrategy = ({ />
-
({ - constraintsContainer: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - marginTop: '0.5rem', - width: '100%', - }, - constraint: { - fontSize: theme.fontSizes.smallBody, - alignItems: 'center;', - margin: '0.5rem 0', - display: 'flex', - border: `1px solid ${theme.palette.grey[300]}`, - padding: '0.2rem', - borderRadius: '5px', - }, - constraintName: { - minWidth: '100px', - marginRight: '0.5rem', - }, - constraintOperator: { - marginRight: '0.5rem', - }, - constraintValues: { - textOverflow: 'ellipsis', - overflow: 'hidden', - maxWidth: '50%', - }, - text: { - textAlign: 'center', - margin: '0.2rem 0 0.5rem', - display: 'flex', - alignItems: 'center', - }, -})); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.tsx index 9e4b42b004..224032e135 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.tsx @@ -3,13 +3,12 @@ import { IConstraint, IFeatureStrategy, IParameter } from 'interfaces/strategy'; import ConditionallyRender from 'component/common/ConditionallyRender'; import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle'; import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator'; -import { useStyles } from './FeatureOverviewExecution.styles'; import FeatureOverviewExecutionChips from './FeatureOverviewExecutionChips/FeatureOverviewExecutionChips'; import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies'; -import Constraint from 'component/common/Constraint/Constraint'; import StringTruncator from 'component/common/StringTruncator/StringTruncator'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { FeatureOverviewSegment } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment'; +import { ConstraintAccordionList } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList'; interface IFeatureOverviewExecutionProps { parameters: IParameter; @@ -23,46 +22,27 @@ const FeatureOverviewExecution = ({ constraints = [], strategy, }: IFeatureOverviewExecutionProps) => { - const styles = useStyles(); const { strategies } = useStrategies(); const { uiConfig } = useUiConfig(); - if (!parameters) return null; + if (!parameters) { + return null; + } const definition = strategies.find(strategyDefinition => { return strategyDefinition.name === strategy.name; }); - const renderConstraints = () => { - return constraints.map((constraint, index) => { - if (index !== constraints.length - 1) { - return ( - - - - - - ); - } - return ( - - ); - }); - }; - const renderParameters = () => { if (definition?.editable) return null; - return Object.keys(parameters).map((key, index) => { + return Object.keys(parameters).map(key => { switch (key) { case 'rollout': case 'Rollout': return ( -

+

{parameters[key]}% of your base{' '} {constraints.length > 0 ? 'who match constraints' @@ -145,7 +125,7 @@ const FeatureOverviewExecution = ({ case 'percentage': return ( -

+

{strategy?.parameters[param.name]}% of your base{' '} {constraints?.length > 0 ? 'who match constraints' @@ -165,7 +145,7 @@ const FeatureOverviewExecution = ({ case 'boolean': return ( -

+

-

+

-

+

0} show={ -

- {renderConstraints()} + <> + -
+ } /> ({ padding: '1rem', fontSize: theme.fontSizes.smallBody, backgroundColor: theme.palette.grey[200], - margin: '0.5rem 0', position: 'relative', borderRadius: '5px', textAlign: 'center', diff --git a/frontend/src/constants/operators.ts b/frontend/src/constants/operators.ts index 2fe275d8ac..0cfcd56828 100644 --- a/frontend/src/constants/operators.ts +++ b/frontend/src/constants/operators.ts @@ -32,29 +32,31 @@ export const SEMVER_GT = 'SEMVER_GT'; export const SEMVER_LT = 'SEMVER_LT'; export const allOperators: Operator[] = [ - NOT_IN, IN, - STR_ENDS_WITH, - STR_STARTS_WITH, + NOT_IN, STR_CONTAINS, + STR_STARTS_WITH, + STR_ENDS_WITH, NUM_EQ, NUM_GT, NUM_GTE, NUM_LT, NUM_LTE, - DATE_AFTER, DATE_BEFORE, + DATE_AFTER, SEMVER_EQ, SEMVER_GT, SEMVER_LT, ]; export const stringOperators: Operator[] = [ - STR_ENDS_WITH, - STR_STARTS_WITH, STR_CONTAINS, + STR_STARTS_WITH, + STR_ENDS_WITH, ]; + export const inOperators: Operator[] = [IN, NOT_IN]; + export const numOperators: Operator[] = [ NUM_EQ, NUM_GT, @@ -62,7 +64,9 @@ export const numOperators: Operator[] = [ NUM_LT, NUM_LTE, ]; -export const dateOperators: Operator[] = [DATE_AFTER, DATE_BEFORE]; + +export const dateOperators: Operator[] = [DATE_BEFORE, DATE_AFTER]; + export const semVerOperators: Operator[] = [SEMVER_EQ, SEMVER_GT, SEMVER_LT]; export const singleValueOperators: Operator[] = [ diff --git a/frontend/src/component/common/Constraint/formatConstraintValue.ts b/frontend/src/utils/formatConstraintValue.ts similarity index 85% rename from frontend/src/component/common/Constraint/formatConstraintValue.ts rename to frontend/src/utils/formatConstraintValue.ts index 8cef3e9bc3..c33e875c3d 100644 --- a/frontend/src/component/common/Constraint/formatConstraintValue.ts +++ b/frontend/src/utils/formatConstraintValue.ts @@ -1,7 +1,7 @@ import { IConstraint } from 'interfaces/strategy'; -import { CURRENT_TIME_CONTEXT_FIELD } from 'component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditHeader/ConstraintAccordionEditHeader'; import { formatDateYMDHMS } from 'utils/formatDate'; import { ILocationSettings } from 'hooks/useLocationSettings'; +import { CURRENT_TIME_CONTEXT_FIELD } from 'utils/operatorsForContext'; export const formatConstraintValuesOrValue = ( constraint: IConstraint, diff --git a/frontend/src/utils/operatorUtils.ts b/frontend/src/utils/operatorsForContext.ts similarity index 78% rename from frontend/src/utils/operatorUtils.ts rename to frontend/src/utils/operatorsForContext.ts index 1c31802ea3..257552abd4 100644 --- a/frontend/src/utils/operatorUtils.ts +++ b/frontend/src/utils/operatorsForContext.ts @@ -1,7 +1,8 @@ -import { CURRENT_TIME_CONTEXT_FIELD } from 'component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditHeader/ConstraintAccordionEditHeader'; import { allOperators, dateOperators, Operator } from 'constants/operators'; import { oneOf } from 'utils/oneOf'; +export const CURRENT_TIME_CONTEXT_FIELD = 'currentTime'; + export const operatorsForContext = (contextName: string): Operator[] => { return allOperators.filter(operator => { if (