1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-17 13:46:47 +02:00

Merge pull request #1141 from Unleash/task/constraint_card_adjustmnets

Constraint card adjustments
This commit is contained in:
andreas-unleash 2022-07-26 15:58:31 +03:00 committed by GitHub
commit f86bd16c7e
40 changed files with 631 additions and 296 deletions

View File

@ -35,7 +35,8 @@
"fmt": "prettier src --write --loglevel warn", "fmt": "prettier src --write --loglevel warn",
"fmt:check": "prettier src --check", "fmt:check": "prettier src --check",
"e2e": "yarn run cypress open --config baseUrl='http://localhost:3000' --env AUTH_USER=admin,AUTH_PASSWORD=unleash4all", "e2e": "yarn run cypress open --config baseUrl='http://localhost:3000' --env AUTH_USER=admin,AUTH_PASSWORD=unleash4all",
"e2e:heroku": "yarn run cypress open --config baseUrl='http://localhost:3000' --env AUTH_USER=example@example.com" "e2e:heroku": "yarn run cypress open --config baseUrl='http://localhost:3000' --env AUTH_USER=example@example.com",
"isready": "yarn lint && yarn fmt && yarn prepare"
}, },
"devDependencies": { "devDependencies": {
"@emotion/react": "11.9.3", "@emotion/react": "11.9.3",

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.10831 2.60839C3.71687 2.23054 3.09322 2.23475 2.70696 2.62102C2.31643 3.01154 2.31643 3.64471 2.70696 4.03523L10.414 11.7423L10.6238 13.7566C10.6974 14.4631 11.293 14.9998 12.0033 14.9998C12.4551 14.9998 12.8605 14.7827 13.1147 14.4429L19.6775 21.0058C20.068 21.3963 20.7012 21.3963 21.0917 21.0058C21.478 20.6195 21.4822 19.9959 21.1044 19.6044C21.1001 19.6003 21.0958 19.5961 21.0916 19.5919L4.12102 2.62132C4.11674 2.61704 4.1125 2.61273 4.10831 2.60839ZM13.8264 9.4983L14.2443 5.4864C14.3828 4.15689 13.34 2.99985 12.0033 2.99985C10.6859 2.99985 9.65401 4.12375 9.7571 5.42898L13.8264 9.4983ZM12.0033 16.9998C11.0368 16.9998 10.2533 17.7834 10.2533 18.7498C10.2533 19.7163 11.0368 20.4998 12.0033 20.4998C12.9698 20.4998 13.7533 19.7163 13.7533 18.7498C13.7533 17.7834 12.9698 16.9998 12.0033 16.9998Z"/>
</svg>

After

Width:  |  Height:  |  Size: 972 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.0033 3C10.6666 3 9.62387 4.15704 9.76236 5.48654L10.6238 13.7567C10.6974 14.4633 11.293 15 12.0033 15C12.7137 15 13.3093 14.4633 13.3829 13.7567L14.2443 5.48655C14.3828 4.15704 13.34 3 12.0033 3ZM12.0033 17C11.0368 17 10.2533 17.7835 10.2533 18.75C10.2533 19.7165 11.0368 20.5 12.0033 20.5C12.9698 20.5 13.7533 19.7165 13.7533 18.75C13.7533 17.7835 12.9698 17 12.0033 17Z" />
</svg>

After

Width:  |  Height:  |  Size: 540 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.10831 2.60839C3.71687 2.23054 3.09322 2.23475 2.70696 2.62102C2.31643 3.01154 2.31643 3.64471 2.70696 4.03523L8.65871 9.98698L7.07 13.6598C6.8 14.2898 7.27 14.9998 7.96 14.9998C8.35 14.9998 8.7 14.7598 8.85 14.3998L9.5 12.7998H11.4716L15.6716 16.9998H6C5.45 16.9998 5 17.4498 5 17.9998C5 18.5498 5.45 18.9998 6 18.9998H17.6716L19.6775 21.0058C20.068 21.3963 20.7012 21.3963 21.0917 21.0058C21.478 20.6195 21.4822 19.9959 21.1044 19.6044C21.1001 19.6003 21.0958 19.5961 21.0916 19.5919L4.12102 2.62132C4.11674 2.61704 4.1125 2.61273 4.10831 2.60839ZM16.1235 11.7954L13.05 4.68985C12.87 4.26985 12.46 3.99985 12 3.99985C11.54 3.99985 11.13 4.26985 10.95 4.68985L10.3666 6.03851L11.5408 7.21265L12 5.97985L13.0045 8.67635L16.1235 11.7954Z" />
</svg>

After

Width:  |  Height:  |  Size: 903 B

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M5 18C5 18.55 5.45 19 6 19H18C18.55 19 19 18.55 19 18C19 17.45 18.55 17 18 17H6C5.45 17 5 17.45 5 18ZM9.5 12.8H14.5L15.16 14.4C15.31 14.76 15.66 15 16.05 15C16.74 15 17.2 14.29 16.93 13.66L13.05 4.69C12.87 4.27 12.46 4 12 4C11.54 4 11.13 4.27 10.95 4.69L7.07 13.66C6.8 14.29 7.27 15 7.96 15C8.35 15 8.7 14.76 8.85 14.4L9.5 12.8ZM12 5.98L13.87 11H10.13L12 5.98Z" />
</svg>

After

Width:  |  Height:  |  Size: 485 B

View File

@ -6,13 +6,7 @@ import React, {
useState, useState,
VFC, VFC,
} from 'react'; } from 'react';
import { import { Button, Divider, FormControlLabel, Switch } from '@mui/material';
Button,
Divider,
FormControlLabel,
Switch,
TextField,
} from '@mui/material';
import produce from 'immer'; import produce from 'immer';
import { trim } from 'component/common/util'; import { trim } from 'component/common/util';
import { IAddon, IAddonProvider } from 'interfaces/addons'; import { IAddon, IAddonProvider } from 'interfaces/addons';

View File

@ -7,14 +7,7 @@ import {
AutocompleteRenderOptionState, AutocompleteRenderOptionState,
} from '@mui/material/Autocomplete'; } from '@mui/material/Autocomplete';
import { styled } from '@mui/system'; import { styled } from '@mui/system';
import { import { capitalize, Checkbox, Paper, TextField } from '@mui/material';
Autocomplete,
Box,
capitalize,
Checkbox,
Paper,
TextField,
} from '@mui/material';
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
import CheckBoxIcon from '@mui/icons-material/CheckBox'; import CheckBoxIcon from '@mui/icons-material/CheckBox';
import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';

View File

@ -1,14 +1,9 @@
import { Popover, Badge, styled, Tooltip } from '@mui/material'; import { Badge, Popover, styled } from '@mui/material';
import { IGroup, IGroupUser, Role } from 'interfaces/group'; import { IGroupUser, Role } from 'interfaces/group';
import { Link } from 'react-router-dom';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Badge as StyledBadge } from 'component/common/Badge/Badge'; import { Badge as StyledBadge } from 'component/common/Badge/Badge';
import { RemoveGroup } from 'component/admin/groups/RemoveGroup/RemoveGroup';
import { useState } from 'react';
import StarIcon from '@mui/icons-material/Star'; import StarIcon from '@mui/icons-material/Star';
import { IUser } from '../../../../../../../interfaces/user';
const StyledPopover = styled(Popover)(({ theme }) => ({ const StyledPopover = styled(Popover)(({ theme }) => ({
pointerEvents: 'none', pointerEvents: 'none',

View File

@ -17,8 +17,8 @@ export const useStyles = makeStyles()(theme => ({
fill: '#fff', fill: '#fff',
}, },
accordion: { accordion: {
border: `1px solid ${theme.palette.grey[300]}`, border: `1px solid ${theme.palette.grey[400]}`,
borderRadius: '5px', borderRadius: '8px',
backgroundColor: '#fff', backgroundColor: '#fff',
boxShadow: 'none', boxShadow: 'none',
margin: 0, margin: 0,
@ -46,6 +46,11 @@ export const useStyles = makeStyles()(theme => ({
position: 'relative', position: 'relative',
}, },
}, },
headerValuesContainerWrapper: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
},
headerValuesContainer: { headerValuesContainer: {
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
@ -81,12 +86,6 @@ export const useStyles = makeStyles()(theme => ({
top: '-10px', top: '-10px',
}, },
}, },
help: {
fill: theme.palette.grey[600],
[theme.breakpoints.down(860)]: {
display: 'none',
},
},
headerText: { headerText: {
maxWidth: '400px', maxWidth: '400px',
fontSize: theme.fontSizes.smallBody, fontSize: theme.fontSizes.smallBody,
@ -105,8 +104,9 @@ export const useStyles = makeStyles()(theme => ({
[theme.breakpoints.down(770)]: { [theme.breakpoints.down(770)]: {
marginTop: '1rem', marginTop: '1rem',
}, },
display: 'inline-flex',
}, },
headerSelect: { marginRight: '2rem', width: '200px' }, headerSelect: { marginRight: '1rem', width: '200px' },
chip: { chip: {
margin: '0 0.5rem 0.5rem 0', margin: '0 0.5rem 0.5rem 0',
}, },

View File

@ -33,6 +33,7 @@ export const ConstraintAccordion = ({
constraint={constraint} constraint={constraint}
onCancel={onCancel} onCancel={onCancel}
onSave={onSave!} onSave={onSave!}
onDelete={onDelete}
compact={compact} compact={compact}
/> />
} }
@ -41,7 +42,6 @@ export const ConstraintAccordion = ({
constraint={constraint} constraint={constraint}
onEdit={onEdit} onEdit={onEdit}
onDelete={onDelete} onDelete={onDelete}
compact={compact}
/> />
} }
/> />

View File

@ -19,6 +19,7 @@ interface IConstraintAccordionEditProps {
onCancel: () => void; onCancel: () => void;
onSave: (constraint: IConstraint) => void; onSave: (constraint: IConstraint) => void;
compact: boolean; compact: boolean;
onDelete?: () => void;
} }
export const CANCEL = 'cancel'; export const CANCEL = 'cancel';
@ -48,6 +49,7 @@ export const ConstraintAccordionEdit = ({
compact, compact,
onCancel, onCancel,
onSave, onSave,
onDelete,
}: IConstraintAccordionEditProps) => { }: IConstraintAccordionEditProps) => {
const [localConstraint, setLocalConstraint] = useState<IConstraint>( const [localConstraint, setLocalConstraint] = useState<IConstraint>(
cleanConstraint(constraint) cleanConstraint(constraint)
@ -203,6 +205,9 @@ export const ConstraintAccordionEdit = ({
setOperator={setOperator} setOperator={setOperator}
action={action} action={action}
compact={compact} compact={compact}
setInvertedOperator={setInvertedOperator}
setCaseInsensitive={setCaseInsensitive}
onDelete={onDelete}
/> />
</AccordionSummary> </AccordionSummary>
@ -214,10 +219,8 @@ export const ConstraintAccordionEdit = ({
localConstraint={localConstraint} localConstraint={localConstraint}
setValues={setValues} setValues={setValues}
setValue={setValue} setValue={setValue}
setCaseInsensitive={setCaseInsensitive}
triggerTransition={triggerTransition} triggerTransition={triggerTransition}
setAction={setAction} setAction={setAction}
setInvertedOperator={setInvertedOperator}
onSubmit={onSubmit} onSubmit={onSubmit}
> >
<ResolveInput <ResolveInput
@ -229,7 +232,6 @@ export const ConstraintAccordionEdit = ({
error={error} error={error}
contextDefinition={contextDefinition} contextDefinition={contextDefinition}
removeValue={removeValue} removeValue={removeValue}
setCaseInsensitive={setCaseInsensitive}
/> />
</ConstraintAccordionEditBody> </ConstraintAccordionEditBody>
</AccordionDetails> </AccordionDetails>

View File

@ -1,31 +0,0 @@
import { FormControlLabel, Switch } from '@mui/material';
import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader';
interface ICaseInsensitiveProps {
caseInsensitive: boolean;
setCaseInsensitive: (caseInsensitive: boolean) => void;
}
export const CaseInsensitive = ({
caseInsensitive,
setCaseInsensitive,
}: ICaseInsensitiveProps) => {
return (
<>
<ConstraintFormHeader>
Should the constraint be case insensitive?
</ConstraintFormHeader>
<FormControlLabel
style={{ display: 'block' }}
control={
<Switch
checked={caseInsensitive}
onChange={() => setCaseInsensitive(!caseInsensitive)}
color="primary"
/>
}
label="Case insensitive"
/>
</>
);
};

View File

@ -3,6 +3,7 @@ import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({ export const useStyles = makeStyles()(theme => ({
inputContainer: { inputContainer: {
padding: '1rem', padding: '1rem',
backgroundColor: theme.palette.neutral.light,
}, },
buttonContainer: { buttonContainer: {
display: 'flex', display: 'flex',

View File

@ -1,8 +1,7 @@
import { Button, FormControlLabel, Switch } from '@mui/material'; import { Button } from '@mui/material';
import { IConstraint } from 'interfaces/strategy'; import { IConstraint } from 'interfaces/strategy';
import { CANCEL } from '../ConstraintAccordionEdit'; import { CANCEL } from '../ConstraintAccordionEdit';
import { ConstraintFormHeader } from './ConstraintFormHeader/ConstraintFormHeader';
import { useStyles } from './ConstraintAccordionEditBody.styles'; import { useStyles } from './ConstraintAccordionEditBody.styles';
import React from 'react'; import React from 'react';
import { newOperators } from 'constants/operators'; import { newOperators } from 'constants/operators';
@ -16,21 +15,12 @@ interface IConstraintAccordionBody {
triggerTransition: () => void; triggerTransition: () => void;
setValue: (value: string) => void; setValue: (value: string) => void;
setAction: React.Dispatch<React.SetStateAction<string>>; setAction: React.Dispatch<React.SetStateAction<string>>;
setCaseInsensitive: () => void;
setInvertedOperator: () => void;
onSubmit: () => void; onSubmit: () => void;
} }
export const ConstraintAccordionEditBody: React.FC< export const ConstraintAccordionEditBody: React.FC<
IConstraintAccordionBody IConstraintAccordionBody
> = ({ > = ({ localConstraint, children, triggerTransition, setAction, onSubmit }) => {
localConstraint,
children,
triggerTransition,
setInvertedOperator,
setAction,
onSubmit,
}) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
return ( return (
@ -40,10 +30,6 @@ export const ConstraintAccordionEditBody: React.FC<
condition={oneOf(newOperators, localConstraint.operator)} condition={oneOf(newOperators, localConstraint.operator)}
show={<OperatorUpgradeAlert />} show={<OperatorUpgradeAlert />}
/> />
<InvertedOperator
inverted={Boolean(localConstraint.inverted)}
setInvertedOperator={setInvertedOperator}
/>
{children} {children}
</div> </div>
<div className={styles.buttonContainer}> <div className={styles.buttonContainer}>
@ -71,33 +57,3 @@ export const ConstraintAccordionEditBody: React.FC<
</> </>
); );
}; };
interface IInvertedOperatorProps {
inverted: boolean;
setInvertedOperator: () => void;
}
const InvertedOperator = ({
inverted,
setInvertedOperator,
}: IInvertedOperatorProps) => {
return (
<>
<ConstraintFormHeader>
Should the operator be negated? (this will make the operator do
the opposite)
</ConstraintFormHeader>
<FormControlLabel
style={{ display: 'inline-block' }}
control={
<Switch
checked={inverted}
onChange={() => setInvertedOperator()}
color="primary"
/>
}
label="Negated"
/>
</>
);
};

View File

@ -110,8 +110,8 @@ export const FreeTextInput = ({
</div> </div>
<Button <Button
className={styles.button} className={styles.button}
variant="contained" variant="outlined"
color="primary" color="secondary"
onClick={() => addValues()} onClick={() => addValues()}
> >
Add values Add values

View File

@ -1,6 +1,5 @@
import { IUnleashContextDefinition } from 'interfaces/context'; import { IUnleashContextDefinition } from 'interfaces/context';
import { IConstraint } from 'interfaces/strategy'; import { IConstraint } from 'interfaces/strategy';
import { CaseInsensitive } from '../CaseInsensitive/CaseInsensitive';
import { DateSingleValue } from '../DateSingleValue/DateSingleValue'; import { DateSingleValue } from '../DateSingleValue/DateSingleValue';
import { FreeTextInput } from '../FreeTextInput/FreeTextInput'; import { FreeTextInput } from '../FreeTextInput/FreeTextInput';
import { RestrictiveLegalValues } from '../RestrictiveLegalValues/RestrictiveLegalValues'; import { RestrictiveLegalValues } from '../RestrictiveLegalValues/RestrictiveLegalValues';
@ -25,7 +24,6 @@ interface IResolveInputProps {
localConstraint: IConstraint; localConstraint: IConstraint;
setValue: (value: string) => void; setValue: (value: string) => void;
setValues: (values: string[]) => void; setValues: (values: string[]) => void;
setCaseInsensitive: () => void;
setError: React.Dispatch<React.SetStateAction<string>>; setError: React.Dispatch<React.SetStateAction<string>>;
removeValue: (index: number) => void; removeValue: (index: number) => void;
input: Input; input: Input;
@ -38,7 +36,6 @@ export const ResolveInput = ({
localConstraint, localConstraint,
setValue, setValue,
setValues, setValues,
setCaseInsensitive,
setError, setError,
removeValue, removeValue,
error, error,
@ -58,12 +55,6 @@ export const ResolveInput = ({
case STRING_OPERATORS_LEGAL_VALUES: case STRING_OPERATORS_LEGAL_VALUES:
return ( return (
<> <>
<CaseInsensitive
setCaseInsensitive={setCaseInsensitive}
caseInsensitive={Boolean(
localConstraint.caseInsensitive
)}
/>
<RestrictiveLegalValues <RestrictiveLegalValues
legalValues={contextDefinition.legalValues || []} legalValues={contextDefinition.legalValues || []}
values={localConstraint.values || []} values={localConstraint.values || []}
@ -125,13 +116,6 @@ export const ResolveInput = ({
case STRING_OPERATORS_FREETEXT: case STRING_OPERATORS_FREETEXT:
return ( return (
<> <>
{' '}
<CaseInsensitive
setCaseInsensitive={setCaseInsensitive}
caseInsensitive={Boolean(
localConstraint.caseInsensitive
)}
/>
<FreeTextInput <FreeTextInput
values={localConstraint.values || []} values={localConstraint.values || []}
removeValue={removeValue} removeValue={removeValue}

View File

@ -68,7 +68,15 @@ export const RestrictiveLegalValues = ({
<ConstraintFormHeader> <ConstraintFormHeader>
Select values from a predefined set Select values from a predefined set
</ConstraintFormHeader> </ConstraintFormHeader>
<ConstraintValueSearch filter={filter} setFilter={setFilter} /> <ConditionallyRender
condition={legalValues.length > 100}
show={
<ConstraintValueSearch
filter={filter}
setFilter={setFilter}
/>
}
/>
{filteredValues.map(match => ( {filteredValues.map(match => (
<LegalValueLabel <LegalValueLabel
key={match.value} key={match.value}

View File

@ -36,7 +36,15 @@ export const SingleLegalValue = ({
<ConstraintFormHeader> <ConstraintFormHeader>
Add a single {type.toLowerCase()} value Add a single {type.toLowerCase()} value
</ConstraintFormHeader> </ConstraintFormHeader>
<ConstraintValueSearch filter={filter} setFilter={setFilter} /> <ConditionallyRender
condition={Boolean(legalValues.length > 100)}
show={
<ConstraintValueSearch
filter={filter}
setFilter={setFilter}
/>
}
/>
<ConditionallyRender <ConditionallyRender
condition={Boolean(legalValues.length)} condition={Boolean(legalValues.length)}
show={ show={

View File

@ -4,20 +4,25 @@ import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccord
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext'; import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon'; import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon';
import { Help } from '@mui/icons-material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { dateOperators, DATE_AFTER, IN } from 'constants/operators'; import {
import { SAVE } from '../ConstraintAccordionEdit'; dateOperators,
DATE_AFTER,
IN,
stringOperators,
} from 'constants/operators';
import { resolveText } from './helpers'; import { resolveText } from './helpers';
import { oneOf } from 'utils/oneOf'; import { oneOf } from 'utils/oneOf';
import React, { useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { Operator } from 'constants/operators'; import { Operator } from 'constants/operators';
import { ConstraintOperatorSelect } from 'component/common/ConstraintAccordion/ConstraintOperatorSelect/ConstraintOperatorSelect'; import { ConstraintOperatorSelect } from 'component/common/ConstraintAccordion/ConstraintOperatorSelect/ConstraintOperatorSelect';
import { import {
operatorsForContext, operatorsForContext,
CURRENT_TIME_CONTEXT_FIELD, CURRENT_TIME_CONTEXT_FIELD,
} from 'utils/operatorsForContext'; } from 'utils/operatorsForContext';
import { Tooltip } from '@mui/material'; import { InvertedOperatorButton } from '../StyledToggleButton/InvertedOperatorButton/InvertedOperatorButton';
import { CaseSensitiveButton } from '../StyledToggleButton/CaseSensitiveButton/CaseSensitiveButton';
import { ConstraintAccordionHeaderActions } from '../../ConstraintAccordionHeaderActions/ConstraintAccordionHeaderActions';
interface IConstraintAccordionViewHeader { interface IConstraintAccordionViewHeader {
localConstraint: IConstraint; localConstraint: IConstraint;
@ -26,6 +31,9 @@ interface IConstraintAccordionViewHeader {
setLocalConstraint: React.Dispatch<React.SetStateAction<IConstraint>>; setLocalConstraint: React.Dispatch<React.SetStateAction<IConstraint>>;
action: string; action: string;
compact: boolean; compact: boolean;
onDelete?: () => void;
setInvertedOperator: () => void;
setCaseInsensitive: () => void;
} }
export const ConstraintAccordionEditHeader = ({ export const ConstraintAccordionEditHeader = ({
@ -34,11 +42,15 @@ export const ConstraintAccordionEditHeader = ({
setLocalConstraint, setLocalConstraint,
setContextName, setContextName,
setOperator, setOperator,
action, onDelete,
setInvertedOperator,
setCaseInsensitive,
}: IConstraintAccordionViewHeader) => { }: IConstraintAccordionViewHeader) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const { context } = useUnleashContext(); const { context } = useUnleashContext();
const { contextName, operator } = localConstraint; const { contextName, operator } = localConstraint;
const [showCaseSensitiveButton, setShowCaseSensitiveButton] =
useState(false);
/* We need a special case to handle the currenTime context field. Since /* 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 this field will be the only one to allow DATE_BEFORE and DATE_AFTER operators
@ -60,6 +72,8 @@ export const ConstraintAccordionEditHeader = ({
oneOf(dateOperators, operator) oneOf(dateOperators, operator)
) { ) {
setOperator(IN); setOperator(IN);
} else if (oneOf(stringOperators, operator)) {
setShowCaseSensitiveButton(true);
} }
}, [contextName, setOperator, operator, setLocalConstraint]); }, [contextName, setOperator, operator, setLocalConstraint]);
@ -79,6 +93,9 @@ export const ConstraintAccordionEditHeader = ({
value: new Date().toISOString(), value: new Date().toISOString(),
})); }));
} else { } else {
if (oneOf(stringOperators, operator)) {
setShowCaseSensitiveButton(true);
}
setOperator(operator); setOperator(operator);
} }
}; };
@ -100,6 +117,10 @@ export const ConstraintAccordionEditHeader = ({
/> />
</div> </div>
<div className={styles.bottomSelect}> <div className={styles.bottomSelect}>
<InvertedOperatorButton
localConstraint={localConstraint}
setInvertedOperator={setInvertedOperator}
/>
<div className={styles.headerSelect}> <div className={styles.headerSelect}>
<ConstraintOperatorSelect <ConstraintOperatorSelect
options={operatorsForContext(contextName)} options={operatorsForContext(contextName)}
@ -107,6 +128,15 @@ export const ConstraintAccordionEditHeader = ({
onChange={onOperatorChange} onChange={onOperatorChange}
/> />
</div> </div>
<ConditionallyRender
condition={showCaseSensitiveButton}
show={
<CaseSensitiveButton
localConstraint={localConstraint}
setCaseInsensitive={setCaseInsensitive}
/>
}
/>
</div> </div>
</div> </div>
<ConditionallyRender <ConditionallyRender
@ -117,21 +147,7 @@ export const ConstraintAccordionEditHeader = ({
</p> </p>
} }
/> />
<ConditionallyRender <ConstraintAccordionHeaderActions onDelete={onDelete} disableEdit />
condition={action === SAVE}
show={<p className={styles.editingBadge}>Updating...</p>}
elseShow={<p className={styles.editingBadge}>Editing</p>}
/>
<Tooltip title="Help" arrow>
<a
href="https://docs.getunleash.io/advanced/strategy_constraints"
style={{ marginLeft: 'auto' }}
target="_blank"
rel="noopener noreferrer"
>
<Help className={styles.help} />
</a>
</Tooltip>
</div> </div>
); );
}; };

View File

@ -0,0 +1,46 @@
import { Tooltip } from '@mui/material';
import { ReactComponent as CaseSensitive } from 'assets/icons/24_Text format.svg';
import { ReactComponent as CaseSensitiveOff } from 'assets/icons/24_Text format off.svg';
import React from 'react';
import {
StyledToggleButtonOff,
StyledToggleButtonOn,
} from '../StyledToggleButton';
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { IConstraint } from '../../../../../../interfaces/strategy';
interface CaseSensitiveButtonProps {
localConstraint: IConstraint;
setCaseInsensitive: () => void;
}
export const CaseSensitiveButton = ({
localConstraint,
setCaseInsensitive,
}: CaseSensitiveButtonProps) => {
return (
<ConditionallyRender
condition={Boolean(localConstraint.caseInsensitive)}
show={
<Tooltip title="Make it case sensitive" arrow>
<StyledToggleButtonOff
onClick={setCaseInsensitive}
disableRipple
>
<CaseSensitiveOff />
</StyledToggleButtonOff>
</Tooltip>
}
elseShow={
<Tooltip title="Make it case insensitive" arrow>
<StyledToggleButtonOn
onClick={setCaseInsensitive}
disableRipple
>
<CaseSensitive />
</StyledToggleButtonOn>
</Tooltip>
}
/>
);
};

View File

@ -0,0 +1,46 @@
import { Tooltip } from '@mui/material';
import { ReactComponent as NegatedIcon } from '../../../../../../assets/icons/24_Negator.svg';
import { ReactComponent as NegatedIconOff } from '../../../../../../assets/icons/24_Negator off.svg';
import React from 'react';
import { IConstraint } from '../../../../../../interfaces/strategy';
import {
StyledToggleButtonOff,
StyledToggleButtonOn,
} from '../StyledToggleButton';
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
interface InvertedOperatorButtonProps {
localConstraint: IConstraint;
setInvertedOperator: () => void;
}
export const InvertedOperatorButton = ({
localConstraint,
setInvertedOperator,
}: InvertedOperatorButtonProps) => {
return (
<ConditionallyRender
condition={Boolean(localConstraint.inverted)}
show={
<Tooltip title="Remove negation" arrow>
<StyledToggleButtonOn
onClick={setInvertedOperator}
disableRipple
>
<NegatedIcon />
</StyledToggleButtonOn>
</Tooltip>
}
elseShow={
<Tooltip title="Negate operator" arrow>
<StyledToggleButtonOff
onClick={setInvertedOperator}
disableRipple
>
<NegatedIconOff />
</StyledToggleButtonOff>
</Tooltip>
}
/>
);
};

View File

@ -0,0 +1,30 @@
import { styled } from '@mui/system';
import { IconButton } from '@mui/material';
export const StyledToggleButtonOff = styled(IconButton)(({ theme }) => ({
width: '28px',
minWidth: '28px',
maxWidth: '28px',
backgroundColor: theme.palette.tertiary.background,
borderRadius: theme.shape.borderRadius,
padding: '0 1px 0',
marginRight: '1rem',
'&:hover': {
background: theme.palette.tertiary.contrast[300],
},
}));
export const StyledToggleButtonOn = styled(IconButton)(({ theme }) => ({
width: '28px',
minWidth: '28px',
maxWidth: '28px',
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.main,
borderRadius: theme.shape.borderRadius,
marginRight: '1rem',
padding: '0 1px 0',
'&:hover': {
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.main,
},
}));

View File

@ -0,0 +1,67 @@
import React from 'react';
import { IconButton, Tooltip } from '@mui/material';
import { Delete, Edit } from '@mui/icons-material';
import { useStyles } from '../ConstraintAccordion.styles';
import { ConditionallyRender } from '../../ConditionallyRender/ConditionallyRender';
interface ConstraintAccordionHeaderActionsProps {
onDelete?: () => void;
onEdit?: () => void;
disableEdit?: boolean;
disableDelete?: boolean;
}
export const ConstraintAccordionHeaderActions = ({
onEdit,
onDelete,
disableDelete = false,
disableEdit = false,
}: ConstraintAccordionHeaderActionsProps) => {
const { classes: styles } = useStyles();
const onEditClick =
onEdit &&
((event: React.SyntheticEvent) => {
event.stopPropagation();
onEdit();
});
const onDeleteClick =
onDelete &&
((event: React.SyntheticEvent) => {
event.stopPropagation();
onDelete();
});
return (
<div className={styles.headerActions}>
<ConditionallyRender
condition={Boolean(onEditClick) && !disableEdit}
show={
<Tooltip title="Edit constraint" arrow>
<IconButton
type="button"
onClick={onEditClick}
disabled={disableEdit}
>
<Edit />
</IconButton>
</Tooltip>
}
/>
<ConditionallyRender
condition={Boolean(onDeleteClick) && !disableDelete}
show={
<Tooltip title="Delete constraint" arrow>
<IconButton
type="button"
onClick={onDeleteClick}
disabled={disableDelete}
>
<Delete />
</IconButton>
</Tooltip>
}
/>
</div>
);
};

View File

@ -6,4 +6,17 @@ export const useStyles = makeStyles()(theme => ({
display: 'grid', display: 'grid',
gap: '1rem', gap: '1rem',
}, },
help: {
fill: theme.palette.grey[600],
[theme.breakpoints.down(860)]: {
display: 'none',
},
marginLeft: '0.75rem',
},
addCustomLabel: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'start',
margin: '0.75rem 0',
},
})); }));

View File

@ -8,7 +8,8 @@ import { objectId } from 'utils/objectId';
import { useStyles } from './ConstraintAccordionList.styles'; import { useStyles } from './ConstraintAccordionList.styles';
import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint'; import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Button } from '@mui/material'; import { Button, Tooltip } from '@mui/material';
import { Help } from '@mui/icons-material';
interface IConstraintAccordionListProps { interface IConstraintAccordionListProps {
constraints: IConstraint[]; constraints: IConstraint[];
@ -102,11 +103,25 @@ export const ConstraintAccordionList = forwardRef<
condition={Boolean(showCreateButton && onAdd)} condition={Boolean(showCreateButton && onAdd)}
show={ show={
<div> <div>
<div className={styles.addCustomLabel}>
<p>Add any number of custom constraints</p>
<Tooltip title="Help" arrow>
<a
href={
'https://docs.getunleash.io/advanced/strategy_constraints'
}
target="_blank"
rel="noopener noreferrer"
>
<Help className={styles.help} />
</a>
</Tooltip>
</div>
<Button <Button
type="button" type="button"
onClick={onAdd} onClick={onAdd}
variant="text" variant="outlined"
color="primary" color="secondary"
> >
Add custom constraint Add custom constraint
</Button> </Button>

View File

@ -1,5 +1,4 @@
import { Accordion, AccordionSummary, AccordionDetails } from '@mui/material'; import { Accordion, AccordionSummary, AccordionDetails } from '@mui/material';
import { ExpandMore } from '@mui/icons-material';
import { IConstraint } from 'interfaces/strategy'; import { IConstraint } from 'interfaces/strategy';
import { ConstraintAccordionViewBody } from './ConstraintAccordionViewBody/ConstraintAccordionViewBody'; import { ConstraintAccordionViewBody } from './ConstraintAccordionViewBody/ConstraintAccordionViewBody';
@ -12,41 +11,52 @@ import {
} from 'constants/operators'; } from 'constants/operators';
import { useStyles } from '../ConstraintAccordion.styles'; import { useStyles } from '../ConstraintAccordion.styles';
import { useState } from 'react';
interface IConstraintAccordionViewProps { interface IConstraintAccordionViewProps {
constraint: IConstraint; constraint: IConstraint;
onDelete?: () => void; onDelete?: () => void;
onEdit?: () => void; onEdit?: () => void;
compact: boolean;
} }
export const ConstraintAccordionView = ({ export const ConstraintAccordionView = ({
compact,
constraint, constraint,
onEdit, onEdit,
onDelete, onDelete,
}: IConstraintAccordionViewProps) => { }: IConstraintAccordionViewProps) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const [expandable, setExpandable] = useState(true);
const [expanded, setExpanded] = useState(false);
const singleValue = oneOf( const singleValue = oneOf(
[...semVerOperators, ...numOperators, ...dateOperators], [...semVerOperators, ...numOperators, ...dateOperators],
constraint.operator constraint.operator
); );
const handleClick = () => {
if (expandable) {
setExpanded(!expanded);
}
};
return ( return (
<Accordion <Accordion
className={styles.accordion} className={styles.accordion}
classes={{ root: styles.accordionRoot }} classes={{ root: styles.accordionRoot }}
expanded={expanded}
sx={{ cursor: expandable ? 'pointer' : 'default' }}
> >
<AccordionSummary <AccordionSummary
className={styles.summary} className={styles.summary}
expandIcon={<ExpandMore titleAccess="Toggle" />} expandIcon={null}
onClick={handleClick}
> >
<ConstraintAccordionViewHeader <ConstraintAccordionViewHeader
compact={compact}
constraint={constraint} constraint={constraint}
onEdit={onEdit} onEdit={onEdit}
onDelete={onDelete} onDelete={onDelete}
singleValue={singleValue} singleValue={singleValue}
allowExpand={setExpandable}
expanded={expanded}
/> />
</AccordionSummary> </AccordionSummary>

View File

@ -1,149 +1,43 @@
import { Chip, IconButton, Tooltip, styled } from '@mui/material';
import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon'; import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon';
import { Delete, Edit } from '@mui/icons-material';
import { IConstraint } from 'interfaces/strategy'; import { IConstraint } from 'interfaces/strategy';
import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles'; import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import React from 'react'; import React from 'react';
import { formatConstraintValue } from 'utils/formatConstraintValue'; import { ConstraintAccordionViewHeaderInfo } from './ConstraintAccordionViewHeaderInfo/ConstraintAccordionViewHeaderInfo';
import { useLocationSettings } from 'hooks/useLocationSettings'; import { ConstraintAccordionHeaderActions } from '../../ConstraintAccordionHeaderActions/ConstraintAccordionHeaderActions';
import { ConstraintOperator } from 'component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator';
import classnames from 'classnames';
const StyledHeaderText = styled('span')(({ theme }) => ({
display: '-webkit-box',
WebkitLineClamp: 3,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
maxWidth: '100px',
minWidth: '100px',
marginRight: '10px',
wordBreak: 'break-word',
fontSize: theme.fontSizes.smallBody,
[theme.breakpoints.down(710)]: {
textAlign: 'center',
padding: theme.spacing(1, 0),
marginRight: 'inherit',
maxWidth: 'inherit',
},
}));
const StyledValuesSpan = styled('span')(({ theme }) => ({
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
wordBreak: 'break-word',
fontSize: theme.fontSizes.smallBody,
[theme.breakpoints.down(710)]: {
margin: theme.spacing(1, 0),
textAlign: 'center',
},
}));
const StyledSingleValueChip = styled(Chip)(({ theme }) => ({
[theme.breakpoints.down(710)]: {
margin: theme.spacing(1, 0),
},
}));
interface IConstraintAccordionViewHeaderProps { interface IConstraintAccordionViewHeaderProps {
compact: boolean;
constraint: IConstraint; constraint: IConstraint;
onDelete?: () => void; onDelete?: () => void;
onEdit?: () => void; onEdit?: () => void;
singleValue: boolean; singleValue: boolean;
expanded: boolean;
allowExpand: (shouldExpand: boolean) => void;
} }
export const ConstraintAccordionViewHeader = ({ export const ConstraintAccordionViewHeader = ({
compact,
constraint, constraint,
onEdit, onEdit,
onDelete, onDelete,
singleValue, singleValue,
allowExpand,
expanded,
}: IConstraintAccordionViewHeaderProps) => { }: IConstraintAccordionViewHeaderProps) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const { locationSettings } = useLocationSettings();
const onEditClick =
onEdit &&
((event: React.SyntheticEvent) => {
event.stopPropagation();
onEdit();
});
const onDeleteClick =
onDelete &&
((event: React.SyntheticEvent) => {
event.stopPropagation();
onDelete();
});
return ( return (
<div className={styles.headerContainer}> <div className={styles.headerContainer}>
<ConstraintIcon /> <ConstraintIcon />
<div className={styles.headerMetaInfo}> <ConstraintAccordionViewHeaderInfo
<Tooltip title={constraint.contextName} arrow> constraint={constraint}
<StyledHeaderText> singleValue={singleValue}
{constraint.contextName} allowExpand={allowExpand}
</StyledHeaderText> expanded={expanded}
</Tooltip> />
<div className={styles.headerConstraintContainer}> <ConstraintAccordionHeaderActions
<ConstraintOperator constraint={constraint} /> onEdit={onEdit}
</div> onDelete={onDelete}
<ConditionallyRender />
condition={singleValue}
show={
<StyledSingleValueChip
label={formatConstraintValue(
constraint,
locationSettings
)}
/>
}
elseShow={
<div className={styles.headerValuesContainer}>
<StyledValuesSpan>
{constraint?.values
?.map(value => value)
.join(', ')}
</StyledValuesSpan>
<p
className={classnames(
styles.headerValuesExpand,
'valuesExpandLabel'
)}
>
Expand to view all ({constraint?.values?.length}
)
</p>
</div>
}
/>
</div>
<div className={styles.headerActions}>
<ConditionallyRender
condition={Boolean(onEditClick)}
show={() => (
<Tooltip title="Edit constraint" arrow>
<IconButton type="button" onClick={onEditClick}>
<Edit />
</IconButton>
</Tooltip>
)}
/>
<ConditionallyRender
condition={Boolean(onDeleteClick)}
show={() => (
<Tooltip title="Delete constraint" arrow>
<IconButton type="button" onClick={onDeleteClick}>
<Delete />
</IconButton>
</Tooltip>
)}
/>
</div>
</div> </div>
); );
}; };

View File

@ -0,0 +1,65 @@
import { styled, Tooltip } from '@mui/material';
import { ConstraintViewHeaderOperator } from '../ConstraintViewHeaderOperator/ConstraintViewHeaderOperator';
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { ContraintAccordionViewHeaderSingleValue } from '../ContraintAccordionViewHeaderSingleValue/ContraintAccordionViewHeaderSingleValue';
import { ConstraintAccordionViewHeaderMultipleValues } from '../ContraintAccordionViewHeaderMultipleValues/ConstraintAccordionViewHeaderMultipleValues';
import React from 'react';
import { IConstraint } from '../../../../../../interfaces/strategy';
import { useStyles } from '../../../ConstraintAccordion.styles';
const StyledHeaderText = styled('span')(({ theme }) => ({
display: '-webkit-box',
WebkitLineClamp: 3,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
maxWidth: '100px',
minWidth: '100px',
marginRight: '10px',
wordBreak: 'break-word',
fontSize: theme.fontSizes.smallBody,
[theme.breakpoints.down(710)]: {
textAlign: 'center',
padding: theme.spacing(1, 0),
marginRight: 'inherit',
maxWidth: 'inherit',
},
}));
interface ConstraintAccordionViewHeaderMetaInfoProps {
constraint: IConstraint;
singleValue: boolean;
expanded: boolean;
allowExpand: (shouldExpand: boolean) => void;
}
export const ConstraintAccordionViewHeaderInfo = ({
constraint,
singleValue,
allowExpand,
expanded,
}: ConstraintAccordionViewHeaderMetaInfoProps) => {
const { classes: styles } = useStyles();
return (
<div className={styles.headerMetaInfo}>
<Tooltip title={constraint.contextName} arrow>
<StyledHeaderText>{constraint.contextName}</StyledHeaderText>
</Tooltip>
<ConstraintViewHeaderOperator constraint={constraint} />
<ConditionallyRender
condition={singleValue}
show={
<ContraintAccordionViewHeaderSingleValue
constraint={constraint}
/>
}
elseShow={
<ConstraintAccordionViewHeaderMultipleValues
constraint={constraint}
expanded={expanded}
allowExpand={allowExpand}
/>
}
/>
</div>
);
};

View File

@ -0,0 +1,36 @@
import { IConstraint } from '../../../../../../interfaces/strategy';
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { Tooltip } from '@mui/material';
import { ReactComponent as NegatedIcon } from '../../../../../../assets/icons/24_Negator.svg';
import { ConstraintOperator } from '../../../ConstraintOperator/ConstraintOperator';
import React from 'react';
import { useStyles } from '../../../ConstraintAccordion.styles';
import { StyledIconWrapper } from '../StyledIconWrapper/StyledIconWrapper';
interface ConstraintViewHeaderOperatorProps {
constraint: IConstraint;
}
export const ConstraintViewHeaderOperator = ({
constraint,
}: ConstraintViewHeaderOperatorProps) => {
const { classes: styles } = useStyles();
return (
<div className={styles.headerValuesContainerWrapper}>
<ConditionallyRender
condition={Boolean(constraint.inverted)}
show={
<Tooltip title={'Operator is negated'} arrow>
<StyledIconWrapper marginright={'0'}>
<NegatedIcon />
</StyledIconWrapper>
</Tooltip>
}
/>
<div className={styles.headerConstraintContainer}>
<ConstraintOperator constraint={constraint} />
</div>
</div>
);
};

View File

@ -0,0 +1,102 @@
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { oneOf } from '../../../../../../utils/oneOf';
import { stringOperators } from '../../../../../../constants/operators';
import { styled, Tooltip } from '@mui/material';
import { ReactComponent as CaseSensitive } from '../../../../../../assets/icons/24_Text format.svg';
import React, { useEffect, useRef, useState } from 'react';
import classnames from 'classnames';
import { StyledIconWrapper } from '../StyledIconWrapper/StyledIconWrapper';
import { IConstraint } from '../../../../../../interfaces/strategy';
import { useStyles } from '../../../ConstraintAccordion.styles';
import { getTextWidth } from '../../../helpers';
const StyledValuesSpan = styled('span')(({ theme }) => ({
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
wordBreak: 'break-word',
fontSize: theme.fontSizes.smallBody,
[theme.breakpoints.down(710)]: {
margin: theme.spacing(1, 0),
textAlign: 'center',
},
}));
interface ConstraintSingleValueProps {
constraint: IConstraint;
expanded: boolean;
allowExpand: (shouldExpand: boolean) => void;
}
export const ConstraintAccordionViewHeaderMultipleValues = ({
constraint,
expanded,
allowExpand,
}: ConstraintSingleValueProps) => {
const { classes: styles } = useStyles();
const elementRef = useRef<HTMLElement>(null);
const [width, setWidth] = useState(0);
const [textWidth, setTextWidth] = useState(0);
const [expandable, setExpandable] = useState(false);
useEffect(() => {
if (elementRef && elementRef.current != null) {
setTextWidth(
Math.round(getTextWidth(elementRef.current.innerText) / 2) // 2 lines
);
setWidth(elementRef.current.clientWidth);
}
}, []);
useEffect(() => {
if (textWidth && width) {
setExpandable(textWidth > width);
}
}, [textWidth, width]);
useEffect(() => {
allowExpand(expandable);
}, [expandable, allowExpand]);
return (
<div className={styles.headerValuesContainerWrapper}>
<ConditionallyRender
condition={
!Boolean(constraint.caseInsensitive) &&
oneOf(stringOperators, constraint.operator)
}
show={
<Tooltip title="Case sensitive is active" arrow>
<StyledIconWrapper>
<CaseSensitive />
</StyledIconWrapper>
</Tooltip>
}
/>
<div className={styles.headerValuesContainer}>
<StyledValuesSpan ref={elementRef}>
{constraint?.values?.map(value => value).join(', ')}
</StyledValuesSpan>
<ConditionallyRender
condition={expandable}
show={
<p
className={classnames(
styles.headerValuesExpand,
'valuesExpandLabel'
)}
>
{!expanded
? `View all (
${constraint?.values?.length})`
: 'View less'}
</p>
}
/>
</div>
</div>
);
};

View File

@ -0,0 +1,49 @@
import { ConditionallyRender } from '../../../../ConditionallyRender/ConditionallyRender';
import { oneOf } from '../../../../../../utils/oneOf';
import { stringOperators } from '../../../../../../constants/operators';
import { Chip, styled, Tooltip } from '@mui/material';
import { ReactComponent as CaseSensitive } from '../../../../../../assets/icons/24_Text format.svg';
import { formatConstraintValue } from '../../../../../../utils/formatConstraintValue';
import React from 'react';
import { useStyles } from '../../../ConstraintAccordion.styles';
import { StyledIconWrapper } from '../StyledIconWrapper/StyledIconWrapper';
import { IConstraint } from '../../../../../../interfaces/strategy';
import { useLocationSettings } from '../../../../../../hooks/useLocationSettings';
const StyledSingleValueChip = styled(Chip)(({ theme }) => ({
[theme.breakpoints.down(710)]: {
margin: theme.spacing(1, 0),
},
}));
interface ConstraintSingleValueProps {
constraint: IConstraint;
}
export const ContraintAccordionViewHeaderSingleValue = ({
constraint,
}: ConstraintSingleValueProps) => {
const { locationSettings } = useLocationSettings();
const { classes: styles } = useStyles();
return (
<div className={styles.headerValuesContainerWrapper}>
<ConditionallyRender
condition={
!Boolean(constraint.caseInsensitive) &&
oneOf(stringOperators, constraint.operator)
}
show={
<Tooltip title="Case sensitive is active" arrow>
<StyledIconWrapper>
<CaseSensitive />{' '}
</StyledIconWrapper>
</Tooltip>
}
/>
<StyledSingleValueChip
label={formatConstraintValue(constraint, locationSettings)}
/>
</div>
);
};

View File

@ -0,0 +1,16 @@
import { styled } from '@mui/material';
export const StyledIconWrapper = styled('div')<{
marginright?: string;
}>(({ theme, marginright }) => ({
backgroundColor: theme.palette.grey[200],
width: 28,
height: 48,
display: 'inline-flex',
justifyContent: 'center',
padding: '10px 0',
color: theme.palette.primary.main,
marginRight: marginright ? marginright : '1rem',
marginTop: 'auto',
marginBottom: 'auto',
}));

View File

@ -1,6 +1,7 @@
import { IConstraint } from 'interfaces/strategy'; import { IConstraint } from 'interfaces/strategy';
import { formatOperatorDescription } from 'component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription'; import { formatOperatorDescription } from 'component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription';
import { useStyles } from 'component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.styles'; import { useStyles } from 'component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.styles';
import React from 'react';
interface IConstraintOperatorProps { interface IConstraintOperatorProps {
constraint: IConstraint; constraint: IConstraint;
@ -14,15 +15,8 @@ export const ConstraintOperator = ({
const operatorName = constraint.operator; const operatorName = constraint.operator;
const operatorText = formatOperatorDescription(constraint.operator); const operatorText = formatOperatorDescription(constraint.operator);
const notLabel = constraint.inverted && (
<div className={styles.not}>
<span>NOT</span>
</div>
);
return ( return (
<div className={styles.container}> <div className={styles.container}>
{notLabel}
<div className={styles.name}>{operatorName}</div> <div className={styles.name}>{operatorName}</div>
<div className={styles.text}>{operatorText}</div> <div className={styles.text}>{operatorText}</div>
</div> </div>

View File

@ -0,0 +1,13 @@
export function getTextWidth(text: string | null) {
if (text != null) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (context != null) {
context.font = getComputedStyle(document.body).font;
return context.measureText(text).width;
}
}
return 0;
}

View File

@ -6,7 +6,6 @@ import {
Typography, Typography,
useTheme, useTheme,
} from '@mui/material'; } from '@mui/material';
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
import useProjects from 'hooks/api/getters/useProjects/useProjects'; import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { GuidanceIndicator } from 'component/common/GuidanceIndicator/GuidanceIndicator'; import { GuidanceIndicator } from 'component/common/GuidanceIndicator/GuidanceIndicator';

View File

@ -22,7 +22,6 @@ import { FeatureStatusCell } from './FeatureStatusCell/FeatureStatusCell';
import { PlaygroundFeatureSchema } from 'hooks/api/actions/usePlayground/playground.model'; import { PlaygroundFeatureSchema } from 'hooks/api/actions/usePlayground/playground.model';
import { Box, Typography, useMediaQuery, useTheme } from '@mui/material'; import { Box, Typography, useMediaQuery, useTheme } from '@mui/material';
import useLoading from 'hooks/useLoading'; import useLoading from 'hooks/useLoading';
import { GuidanceIndicator } from 'component/common/GuidanceIndicator/GuidanceIndicator';
import { VariantCell } from './VariantCell/VariantCell'; import { VariantCell } from './VariantCell/VariantCell';
const defaultSort: SortingRule<string> = { id: 'name' }; const defaultSort: SortingRule<string> = { id: 'name' };

View File

@ -32,7 +32,6 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { IUser } from 'interfaces/user'; import { IUser } from 'interfaces/user';
import { IGroup } from 'interfaces/group'; import { IGroup } from 'interfaces/group';
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
import { mapGroupUsers } from '../../../../hooks/api/getters/useGroup/useGroup';
const StyledAvatar = styled(Avatar)(({ theme }) => ({ const StyledAvatar = styled(Avatar)(({ theme }) => ({
width: theme.spacing(4), width: theme.spacing(4),

View File

@ -1,11 +1,10 @@
import useSWR, { mutate, SWRConfiguration } from 'swr'; import useSWR, { mutate, SWRConfiguration } from 'swr';
import { useState, useEffect, useMemo } from 'react'; import { useState, useEffect } from 'react';
import { formatApiPath } from 'utils/formatPath'; import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler'; import handleErrorResponses from '../httpErrorResponseHandler';
import { IProjectRole } from 'interfaces/role'; import { IProjectRole } from 'interfaces/role';
import { IGroup } from 'interfaces/group'; import { IGroup } from 'interfaces/group';
import { IUser } from 'interfaces/user'; import { IUser } from 'interfaces/user';
import { useGroups } from '../useGroups/useGroups';
import { mapGroupUsers } from '../useGroup/useGroup'; import { mapGroupUsers } from '../useGroup/useGroup';
export enum ENTITY_TYPE { export enum ENTITY_TYPE {

View File

@ -95,6 +95,8 @@ export default createTheme({
light: colors.grey[200], light: colors.grey[200],
main: colors.grey[400], main: colors.grey[400],
dark: colors.grey[600], dark: colors.grey[600],
background: 'white',
contrast: colors.grey[300],
}, },
divider: colors.grey[300], divider: colors.grey[300],
dividerAlternative: colors.grey[400], dividerAlternative: colors.grey[400],

View File

@ -88,6 +88,8 @@ declare module '@mui/material/styles' {
main: string; main: string;
light: string; light: string;
dark: string; dark: string;
background: string;
contrast: string;
}; };
} }