mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-14 00:19:16 +01:00
feat: expand constraint operator descriptions (2) (#858)
* refactor: remove pre-CO constraints list * refactor: improve constraints dropdown order * refactor: simplify prop value * refactor: add missing space around parameter names * refactor: remove constraint accordion box shadow * refactor: show operator descriptions in constraints accordion * refactor: show operator descriptions in constraints dropdown * refactor: use ConstraintAccordionList in FeatureOverviewExecution * refactor: add separators between operators in constraints dropdown * refactor: remove unnecessary comment
This commit is contained in:
parent
e909d22300
commit
f33ca9db4b
@ -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',
|
||||
},
|
||||
}));
|
@ -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<IFeatureViewParams>();
|
||||
|
||||
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 (
|
||||
<div className={classes + ' ' + className} {...rest}>
|
||||
<div className={classes + ' ' + className} {...rest}>
|
||||
<StringTruncator
|
||||
text={constraint.contextName}
|
||||
maxWidth="125"
|
||||
maxLength={25}
|
||||
/>
|
||||
<StrategySeparator text={constraint.operator} maxWidth="none" />
|
||||
<span className={styles.values}>
|
||||
{formatConstraintValuesOrValue(
|
||||
constraint,
|
||||
locationSettings
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={editable}
|
||||
show={
|
||||
<div className={styles.btnContainer}>
|
||||
<PermissionIconButton
|
||||
onClick={editCallback!}
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
disabled={disabledEdit}
|
||||
>
|
||||
<Edit titleAccess="Edit constraint" />
|
||||
</PermissionIconButton>
|
||||
|
||||
<PermissionIconButton
|
||||
onClick={deleteCallback!}
|
||||
permission={UPDATE_FEATURE}
|
||||
projectId={projectId}
|
||||
>
|
||||
<Delete titleAccess="Delete constraint" />
|
||||
</PermissionIconButton>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Constraint;
|
@ -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: {
|
||||
|
@ -97,7 +97,7 @@ const InvertedOperator = ({
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label={'negated'}
|
||||
label="Negated"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -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}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.bottomSelect}>
|
||||
<GeneralSelect
|
||||
id="operator-select"
|
||||
name="operator"
|
||||
label="Operator"
|
||||
options={filteredOperators}
|
||||
value={localConstraint.operator}
|
||||
onChange={onChange}
|
||||
className={styles.headerSelect}
|
||||
/>
|
||||
<div className={styles.headerSelect}>
|
||||
<ConstraintOperatorSelect
|
||||
options={operatorsForContext(contextName)}
|
||||
value={operator}
|
||||
onChange={onOperatorChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={!compact}
|
||||
show={
|
||||
<p className={styles.headerText}>
|
||||
{resolveText(
|
||||
localConstraint.operator,
|
||||
localConstraint.contextName
|
||||
)}
|
||||
{resolveText(operator, contextName)}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={action === SAVE}
|
||||
show={<p className={styles.editingBadge}>Updating...</p>}
|
||||
elseShow={<p className={styles.editingBadge}>Editing</p>}
|
||||
/>
|
||||
|
||||
<a
|
||||
href="https://docs.getunleash.io/advanced/strategy_constraints"
|
||||
style={{ marginLeft: 'auto' }}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { dateOperators } from 'constants/operators';
|
||||
import { IConstraint } from 'interfaces/strategy';
|
||||
import { oneOf } from 'utils/oneOf';
|
||||
import { operatorsForContext } from 'utils/operatorUtils';
|
||||
import { operatorsForContext } from 'utils/operatorsForContext';
|
||||
|
||||
export const createEmptyConstraint = (contextName: string): IConstraint => {
|
||||
const operator = operatorsForContext(contextName)[0];
|
||||
|
@ -41,10 +41,7 @@ export const ConstraintAccordionView = ({
|
||||
return (
|
||||
<Accordion
|
||||
className={styles.accordion}
|
||||
classes={{
|
||||
root: styles.accordionRoot,
|
||||
}}
|
||||
style={{ boxShadow: 'none' }}
|
||||
classes={{ root: styles.accordionRoot }}
|
||||
>
|
||||
<AccordionSummary
|
||||
className={styles.summary}
|
||||
|
@ -8,7 +8,7 @@ import { oneOf } from 'utils/oneOf';
|
||||
import ConditionallyRender from 'component/common/ConditionallyRender';
|
||||
import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles';
|
||||
import { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch';
|
||||
import { formatConstraintValue } from 'component/common/Constraint/formatConstraintValue';
|
||||
import { formatConstraintValue } from 'utils/formatConstraintValue';
|
||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||
|
||||
interface IConstraintAccordionViewBodyProps {
|
||||
|
@ -11,8 +11,9 @@ import { UPDATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/perm
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { IFeatureViewParams } from 'interfaces/params';
|
||||
import React from 'react';
|
||||
import { formatConstraintValue } from 'component/common/Constraint/formatConstraintValue';
|
||||
import { formatConstraintValue } from 'utils/formatConstraintValue';
|
||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||
import { ConstraintOperator } from 'component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator';
|
||||
|
||||
interface IConstraintAccordionViewHeaderProps {
|
||||
compact: boolean;
|
||||
@ -63,13 +64,8 @@ export const ConstraintAccordionViewHeader = ({
|
||||
maxLength={25}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ minWidth: '220px', position: 'relative' }}>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(constraint.inverted)}
|
||||
show={<div className={styles.negated}>NOT</div>}
|
||||
/>
|
||||
<p className={styles.operator}>{constraint.operator}</p>
|
||||
<div className={styles.headerConstraintContainer}>
|
||||
<ConstraintOperator constraint={constraint} />
|
||||
</div>
|
||||
<div className={styles.headerViewValuesContainer}>
|
||||
<ConditionallyRender
|
||||
@ -85,7 +81,10 @@ export const ConstraintAccordionViewHeader = ({
|
||||
elseShow={
|
||||
<div className={styles.headerValuesContainer}>
|
||||
<p className={styles.headerValues}>
|
||||
{constraint?.values?.length} values
|
||||
{constraint?.values?.length}{' '}
|
||||
{constraint?.values?.length === 1
|
||||
? 'value'
|
||||
: 'values'}
|
||||
</p>
|
||||
<p className={styles.headerValuesExpand}>
|
||||
Expand to view
|
||||
|
@ -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',
|
||||
},
|
||||
},
|
||||
}));
|
@ -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 && (
|
||||
<div className={styles.not}>
|
||||
<span>NOT</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{notLabel}
|
||||
<div className={styles.name}>{operatorName}</div>
|
||||
<div className={styles.text}>{operatorText}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -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',
|
||||
};
|
@ -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],
|
||||
},
|
||||
},
|
||||
}));
|
@ -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 (
|
||||
<div className={styles.valueContainer}>
|
||||
<div className={styles.label}>{value}</div>
|
||||
<div className={styles.description}>
|
||||
{formatOperatorDescription(value)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl variant="outlined" size="small" fullWidth>
|
||||
<InputLabel htmlFor="operator-select">Operator</InputLabel>
|
||||
<Select
|
||||
id="operator-select"
|
||||
name="operator"
|
||||
label="Operator"
|
||||
value={value}
|
||||
open={open}
|
||||
onOpen={() => setOpen(true)}
|
||||
onClose={() => setOpen(false)}
|
||||
onChange={onSelectChange}
|
||||
renderValue={renderValue}
|
||||
>
|
||||
{options.map(operator => (
|
||||
<MenuItem
|
||||
key={operator}
|
||||
value={operator}
|
||||
className={classNames(
|
||||
needSeparatorAbove(operator) && styles.separator
|
||||
)}
|
||||
>
|
||||
<div className={styles.optionContainer}>
|
||||
<div className={styles.label}>{operator}</div>
|
||||
<div className={styles.description}>
|
||||
{formatOperatorDescription(operator)}
|
||||
</div>
|
||||
</div>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
const needSeparatorAbove = (operator: Operator): boolean => {
|
||||
const groups = [
|
||||
stringOperators,
|
||||
numOperators,
|
||||
dateOperators,
|
||||
semVerOperators,
|
||||
];
|
||||
|
||||
return groups.some(group => {
|
||||
return group[0] === operator;
|
||||
});
|
||||
};
|
@ -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 (
|
||||
<div
|
||||
style={{
|
||||
@ -17,10 +14,7 @@ export const StrategySeparator = ({
|
||||
padding: '0.1rem 0.25rem',
|
||||
border: `1px solid ${theme.palette.primary.main}`,
|
||||
borderRadius: '0.25rem',
|
||||
maxWidth,
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
textAlign: 'center',
|
||||
margin: '0.5rem 0rem 0.5rem 1rem',
|
||||
backgroundColor: '#fff',
|
||||
}}
|
||||
>
|
||||
|
@ -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<string, string>
|
||||
>({});
|
||||
|
||||
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<IConstraint[]>) => {
|
||||
setStrategy(prev => ({
|
||||
...prev,
|
||||
constraints: value instanceof Function ? value(constraints) : value,
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<List disablePadding dense>
|
||||
{strategy.constraints?.map((constraint, index) => (
|
||||
<ListItem key={index} disableGutters dense>
|
||||
<Constraint
|
||||
constraint={constraint}
|
||||
editCallback={() => setShowConstraintsDialog(true)}
|
||||
deleteCallback={removeConstraint.bind(null, index)}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
<Dialogue
|
||||
title="Define constraints"
|
||||
open={showConstraintsDialog}
|
||||
onClick={onConstraintsDialogSave}
|
||||
primaryButtonText="Update constraints"
|
||||
secondaryButtonText="Cancel"
|
||||
onClose={onConstraintsDialogClose}
|
||||
fullWidth
|
||||
maxWidth="md"
|
||||
>
|
||||
<StrategyConstraints
|
||||
updateConstraints={updateConstraints}
|
||||
constraints={strategy.constraints ?? []}
|
||||
constraintError={constraintErrors}
|
||||
setConstraintError={setConstraintErrors}
|
||||
/>
|
||||
</Dialogue>
|
||||
<PermissionButton
|
||||
onClick={() => setShowConstraintsDialog(true)}
|
||||
variant="text"
|
||||
permission={[UPDATE_FEATURE_STRATEGY, CREATE_FEATURE_STRATEGY]}
|
||||
environmentId={environmentId}
|
||||
projectId={projectId}
|
||||
>
|
||||
Add constraints
|
||||
</PermissionButton>
|
||||
</div>
|
||||
<ConstraintAccordionList
|
||||
projectId={projectId}
|
||||
environmentId={environmentId}
|
||||
constraints={constraints}
|
||||
setConstraints={setConstraints}
|
||||
showCreateButton
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const findConstraintErrors = (
|
||||
constraints: IConstraint[] = []
|
||||
): Record<string, string> => {
|
||||
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;
|
||||
};
|
||||
|
@ -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<IFeatureStrategy>;
|
||||
setStrategy: React.Dispatch<
|
||||
React.SetStateAction<Partial<IFeatureStrategy>>
|
||||
>;
|
||||
}
|
||||
|
||||
export const FeatureStrategyConstraintsCO = ({
|
||||
projectId,
|
||||
environmentId,
|
||||
strategy,
|
||||
setStrategy,
|
||||
}: IFeatureStrategyConstraintsProps) => {
|
||||
const constraints = useMemo(() => {
|
||||
return strategy.constraints ?? [];
|
||||
}, [strategy]);
|
||||
|
||||
const setConstraints = (value: React.SetStateAction<IConstraint[]>) => {
|
||||
setStrategy(prev => ({
|
||||
...prev,
|
||||
constraints: value instanceof Function ? value(constraints) : value,
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<ConstraintAccordionList
|
||||
projectId={projectId}
|
||||
environmentId={environmentId}
|
||||
constraints={constraints}
|
||||
setConstraints={setConstraints}
|
||||
showCreateButton
|
||||
/>
|
||||
);
|
||||
};
|
@ -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 (
|
||||
<form className={styles.form} onSubmit={onSubmitOrProdGuard}>
|
||||
<div>
|
||||
@ -109,9 +100,9 @@ export const FeatureStrategyForm = ({
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(uiConfig.flags.C)}
|
||||
condition={Boolean(uiConfig.flags.C || uiConfig.flags.CO)}
|
||||
show={
|
||||
<FeatureStrategyConstraintsImplementation
|
||||
<FeatureStrategyConstraints
|
||||
projectId={feature.project}
|
||||
environmentId={environmentId}
|
||||
strategy={strategy}
|
||||
@ -120,7 +111,9 @@ export const FeatureStrategyForm = ({
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(uiConfig.flags.SE || uiConfig.flags.C)}
|
||||
condition={Boolean(
|
||||
uiConfig.flags.SE || uiConfig.flags.C || uiConfig.flags.CO
|
||||
)}
|
||||
show={<hr className={styles.hr} />}
|
||||
/>
|
||||
<FeatureStrategyType
|
||||
@ -141,7 +134,7 @@ export const FeatureStrategyForm = ({
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={loading || disableSubmitButtonFromConstraints}
|
||||
disabled={loading || !hasValidConstraints}
|
||||
data-test={STRATEGY_FORM_SUBMIT_ID}
|
||||
>
|
||||
Save strategy
|
||||
|
@ -61,6 +61,7 @@ export const useStyles = makeStyles(theme => ({
|
||||
},
|
||||
separatorText: {
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
textAlign: 'center',
|
||||
padding: '0 1rem',
|
||||
},
|
||||
rightWing: {
|
||||
|
@ -10,28 +10,16 @@ const FeatureOverviewEnvironmentStrategies = ({
|
||||
strategies,
|
||||
environmentName,
|
||||
}: FeatureOverviewEnvironmentStrategiesProps) => {
|
||||
const renderStrategies = () => {
|
||||
return strategies.map(strategy => {
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
{strategies.map(strategy => (
|
||||
<FeatureOverviewEnvironmentStrategy
|
||||
key={strategy.id}
|
||||
strategy={strategy}
|
||||
environmentId={environmentName}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
{renderStrategies()}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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',
|
||||
},
|
||||
}));
|
||||
|
@ -65,7 +65,6 @@ const FeatureOverviewEnvironmentStrategy = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.body}>
|
||||
<FeatureOverviewExecution
|
||||
parameters={parameters}
|
||||
|
@ -1,38 +0,0 @@
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export const useStyles = makeStyles(theme => ({
|
||||
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',
|
||||
},
|
||||
}));
|
@ -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 (
|
||||
<Fragment key={`${constraint.contextName}-${index}`}>
|
||||
<Constraint constraint={constraint} />
|
||||
|
||||
<StrategySeparator text="AND" />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Constraint
|
||||
constraint={constraint}
|
||||
key={`${constraint.contextName}-${index}`}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
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 (
|
||||
<Fragment key={key}>
|
||||
<p className={styles.text}>
|
||||
<p>
|
||||
{parameters[key]}% of your base{' '}
|
||||
{constraints.length > 0
|
||||
? 'who match constraints'
|
||||
@ -145,7 +125,7 @@ const FeatureOverviewExecution = ({
|
||||
case 'percentage':
|
||||
return (
|
||||
<Fragment key={param?.name}>
|
||||
<p className={styles.text}>
|
||||
<p>
|
||||
{strategy?.parameters[param.name]}% of your base{' '}
|
||||
{constraints?.length > 0
|
||||
? 'who match constraints'
|
||||
@ -165,7 +145,7 @@ const FeatureOverviewExecution = ({
|
||||
case 'boolean':
|
||||
return (
|
||||
<Fragment key={param.name}>
|
||||
<p className={styles.text} key={param.name}>
|
||||
<p key={param.name}>
|
||||
<StringTruncator
|
||||
maxLength={15}
|
||||
maxWidth="150"
|
||||
@ -192,7 +172,7 @@ const FeatureOverviewExecution = ({
|
||||
key={param.name}
|
||||
show={
|
||||
<>
|
||||
<p className={styles.text}>
|
||||
<p>
|
||||
<StringTruncator
|
||||
maxWidth="150"
|
||||
maxLength={15}
|
||||
@ -216,7 +196,7 @@ const FeatureOverviewExecution = ({
|
||||
key={param.name}
|
||||
show={
|
||||
<>
|
||||
<p className={styles.text}>
|
||||
<p>
|
||||
<StringTruncator
|
||||
maxLength={15}
|
||||
maxWidth="150"
|
||||
@ -248,10 +228,10 @@ const FeatureOverviewExecution = ({
|
||||
<ConditionallyRender
|
||||
condition={constraints.length > 0}
|
||||
show={
|
||||
<div className={styles.constraintsContainer}>
|
||||
{renderConstraints()}
|
||||
<>
|
||||
<ConstraintAccordionList constraints={constraints} />
|
||||
<StrategySeparator text="AND" />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
|
@ -6,7 +6,6 @@ export const useStyles = makeStyles(theme => ({
|
||||
padding: '1rem',
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
backgroundColor: theme.palette.grey[200],
|
||||
margin: '0.5rem 0',
|
||||
position: 'relative',
|
||||
borderRadius: '5px',
|
||||
textAlign: 'center',
|
||||
|
@ -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[] = [
|
||||
|
@ -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,
|
@ -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 (
|
Loading…
Reference in New Issue
Block a user