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:
commit
f86bd16c7e
@ -35,7 +35,8 @@
|
||||
"fmt": "prettier src --write --loglevel warn",
|
||||
"fmt:check": "prettier src --check",
|
||||
"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": {
|
||||
"@emotion/react": "11.9.3",
|
||||
|
3
frontend/src/assets/icons/24_Negator off.svg
Normal file
3
frontend/src/assets/icons/24_Negator off.svg
Normal 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 |
3
frontend/src/assets/icons/24_Negator.svg
Normal file
3
frontend/src/assets/icons/24_Negator.svg
Normal 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 |
3
frontend/src/assets/icons/24_Text format off.svg
Normal file
3
frontend/src/assets/icons/24_Text format off.svg
Normal 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 |
3
frontend/src/assets/icons/24_Text format.svg
Normal file
3
frontend/src/assets/icons/24_Text format.svg
Normal 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 |
@ -6,13 +6,7 @@ import React, {
|
||||
useState,
|
||||
VFC,
|
||||
} from 'react';
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
TextField,
|
||||
} from '@mui/material';
|
||||
import { Button, Divider, FormControlLabel, Switch } from '@mui/material';
|
||||
import produce from 'immer';
|
||||
import { trim } from 'component/common/util';
|
||||
import { IAddon, IAddonProvider } from 'interfaces/addons';
|
||||
|
@ -7,14 +7,7 @@ import {
|
||||
AutocompleteRenderOptionState,
|
||||
} from '@mui/material/Autocomplete';
|
||||
import { styled } from '@mui/system';
|
||||
import {
|
||||
Autocomplete,
|
||||
Box,
|
||||
capitalize,
|
||||
Checkbox,
|
||||
Paper,
|
||||
TextField,
|
||||
} from '@mui/material';
|
||||
import { capitalize, Checkbox, Paper, TextField } from '@mui/material';
|
||||
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
|
||||
import CheckBoxIcon from '@mui/icons-material/CheckBox';
|
||||
import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';
|
||||
|
@ -1,14 +1,9 @@
|
||||
import { Popover, Badge, styled, Tooltip } from '@mui/material';
|
||||
import { IGroup, IGroupUser, Role } from 'interfaces/group';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Badge, Popover, styled } from '@mui/material';
|
||||
import { IGroupUser, Role } from 'interfaces/group';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
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 { IUser } from '../../../../../../../interfaces/user';
|
||||
|
||||
const StyledPopover = styled(Popover)(({ theme }) => ({
|
||||
pointerEvents: 'none',
|
||||
|
@ -17,8 +17,8 @@ export const useStyles = makeStyles()(theme => ({
|
||||
fill: '#fff',
|
||||
},
|
||||
accordion: {
|
||||
border: `1px solid ${theme.palette.grey[300]}`,
|
||||
borderRadius: '5px',
|
||||
border: `1px solid ${theme.palette.grey[400]}`,
|
||||
borderRadius: '8px',
|
||||
backgroundColor: '#fff',
|
||||
boxShadow: 'none',
|
||||
margin: 0,
|
||||
@ -46,6 +46,11 @@ export const useStyles = makeStyles()(theme => ({
|
||||
position: 'relative',
|
||||
},
|
||||
},
|
||||
headerValuesContainerWrapper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
headerValuesContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@ -81,12 +86,6 @@ export const useStyles = makeStyles()(theme => ({
|
||||
top: '-10px',
|
||||
},
|
||||
},
|
||||
help: {
|
||||
fill: theme.palette.grey[600],
|
||||
[theme.breakpoints.down(860)]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
headerText: {
|
||||
maxWidth: '400px',
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
@ -105,8 +104,9 @@ export const useStyles = makeStyles()(theme => ({
|
||||
[theme.breakpoints.down(770)]: {
|
||||
marginTop: '1rem',
|
||||
},
|
||||
display: 'inline-flex',
|
||||
},
|
||||
headerSelect: { marginRight: '2rem', width: '200px' },
|
||||
headerSelect: { marginRight: '1rem', width: '200px' },
|
||||
chip: {
|
||||
margin: '0 0.5rem 0.5rem 0',
|
||||
},
|
||||
|
@ -33,6 +33,7 @@ export const ConstraintAccordion = ({
|
||||
constraint={constraint}
|
||||
onCancel={onCancel}
|
||||
onSave={onSave!}
|
||||
onDelete={onDelete}
|
||||
compact={compact}
|
||||
/>
|
||||
}
|
||||
@ -41,7 +42,6 @@ export const ConstraintAccordion = ({
|
||||
constraint={constraint}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
compact={compact}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
@ -19,6 +19,7 @@ interface IConstraintAccordionEditProps {
|
||||
onCancel: () => void;
|
||||
onSave: (constraint: IConstraint) => void;
|
||||
compact: boolean;
|
||||
onDelete?: () => void;
|
||||
}
|
||||
|
||||
export const CANCEL = 'cancel';
|
||||
@ -48,6 +49,7 @@ export const ConstraintAccordionEdit = ({
|
||||
compact,
|
||||
onCancel,
|
||||
onSave,
|
||||
onDelete,
|
||||
}: IConstraintAccordionEditProps) => {
|
||||
const [localConstraint, setLocalConstraint] = useState<IConstraint>(
|
||||
cleanConstraint(constraint)
|
||||
@ -203,6 +205,9 @@ export const ConstraintAccordionEdit = ({
|
||||
setOperator={setOperator}
|
||||
action={action}
|
||||
compact={compact}
|
||||
setInvertedOperator={setInvertedOperator}
|
||||
setCaseInsensitive={setCaseInsensitive}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
</AccordionSummary>
|
||||
|
||||
@ -214,10 +219,8 @@ export const ConstraintAccordionEdit = ({
|
||||
localConstraint={localConstraint}
|
||||
setValues={setValues}
|
||||
setValue={setValue}
|
||||
setCaseInsensitive={setCaseInsensitive}
|
||||
triggerTransition={triggerTransition}
|
||||
setAction={setAction}
|
||||
setInvertedOperator={setInvertedOperator}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<ResolveInput
|
||||
@ -229,7 +232,6 @@ export const ConstraintAccordionEdit = ({
|
||||
error={error}
|
||||
contextDefinition={contextDefinition}
|
||||
removeValue={removeValue}
|
||||
setCaseInsensitive={setCaseInsensitive}
|
||||
/>
|
||||
</ConstraintAccordionEditBody>
|
||||
</AccordionDetails>
|
||||
|
@ -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"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -3,6 +3,7 @@ import { makeStyles } from 'tss-react/mui';
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
inputContainer: {
|
||||
padding: '1rem',
|
||||
backgroundColor: theme.palette.neutral.light,
|
||||
},
|
||||
buttonContainer: {
|
||||
display: 'flex',
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Button, FormControlLabel, Switch } from '@mui/material';
|
||||
import { Button } from '@mui/material';
|
||||
import { IConstraint } from 'interfaces/strategy';
|
||||
import { CANCEL } from '../ConstraintAccordionEdit';
|
||||
|
||||
import { ConstraintFormHeader } from './ConstraintFormHeader/ConstraintFormHeader';
|
||||
import { useStyles } from './ConstraintAccordionEditBody.styles';
|
||||
import React from 'react';
|
||||
import { newOperators } from 'constants/operators';
|
||||
@ -16,21 +15,12 @@ interface IConstraintAccordionBody {
|
||||
triggerTransition: () => void;
|
||||
setValue: (value: string) => void;
|
||||
setAction: React.Dispatch<React.SetStateAction<string>>;
|
||||
setCaseInsensitive: () => void;
|
||||
setInvertedOperator: () => void;
|
||||
onSubmit: () => void;
|
||||
}
|
||||
|
||||
export const ConstraintAccordionEditBody: React.FC<
|
||||
IConstraintAccordionBody
|
||||
> = ({
|
||||
localConstraint,
|
||||
children,
|
||||
triggerTransition,
|
||||
setInvertedOperator,
|
||||
setAction,
|
||||
onSubmit,
|
||||
}) => {
|
||||
> = ({ localConstraint, children, triggerTransition, setAction, onSubmit }) => {
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
return (
|
||||
@ -40,10 +30,6 @@ export const ConstraintAccordionEditBody: React.FC<
|
||||
condition={oneOf(newOperators, localConstraint.operator)}
|
||||
show={<OperatorUpgradeAlert />}
|
||||
/>
|
||||
<InvertedOperator
|
||||
inverted={Boolean(localConstraint.inverted)}
|
||||
setInvertedOperator={setInvertedOperator}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
<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"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -110,8 +110,8 @@ export const FreeTextInput = ({
|
||||
</div>
|
||||
<Button
|
||||
className={styles.button}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={() => addValues()}
|
||||
>
|
||||
Add values
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { IUnleashContextDefinition } from 'interfaces/context';
|
||||
import { IConstraint } from 'interfaces/strategy';
|
||||
import { CaseInsensitive } from '../CaseInsensitive/CaseInsensitive';
|
||||
import { DateSingleValue } from '../DateSingleValue/DateSingleValue';
|
||||
import { FreeTextInput } from '../FreeTextInput/FreeTextInput';
|
||||
import { RestrictiveLegalValues } from '../RestrictiveLegalValues/RestrictiveLegalValues';
|
||||
@ -25,7 +24,6 @@ interface IResolveInputProps {
|
||||
localConstraint: IConstraint;
|
||||
setValue: (value: string) => void;
|
||||
setValues: (values: string[]) => void;
|
||||
setCaseInsensitive: () => void;
|
||||
setError: React.Dispatch<React.SetStateAction<string>>;
|
||||
removeValue: (index: number) => void;
|
||||
input: Input;
|
||||
@ -38,7 +36,6 @@ export const ResolveInput = ({
|
||||
localConstraint,
|
||||
setValue,
|
||||
setValues,
|
||||
setCaseInsensitive,
|
||||
setError,
|
||||
removeValue,
|
||||
error,
|
||||
@ -58,12 +55,6 @@ export const ResolveInput = ({
|
||||
case STRING_OPERATORS_LEGAL_VALUES:
|
||||
return (
|
||||
<>
|
||||
<CaseInsensitive
|
||||
setCaseInsensitive={setCaseInsensitive}
|
||||
caseInsensitive={Boolean(
|
||||
localConstraint.caseInsensitive
|
||||
)}
|
||||
/>
|
||||
<RestrictiveLegalValues
|
||||
legalValues={contextDefinition.legalValues || []}
|
||||
values={localConstraint.values || []}
|
||||
@ -125,13 +116,6 @@ export const ResolveInput = ({
|
||||
case STRING_OPERATORS_FREETEXT:
|
||||
return (
|
||||
<>
|
||||
{' '}
|
||||
<CaseInsensitive
|
||||
setCaseInsensitive={setCaseInsensitive}
|
||||
caseInsensitive={Boolean(
|
||||
localConstraint.caseInsensitive
|
||||
)}
|
||||
/>
|
||||
<FreeTextInput
|
||||
values={localConstraint.values || []}
|
||||
removeValue={removeValue}
|
||||
|
@ -68,7 +68,15 @@ export const RestrictiveLegalValues = ({
|
||||
<ConstraintFormHeader>
|
||||
Select values from a predefined set
|
||||
</ConstraintFormHeader>
|
||||
<ConstraintValueSearch filter={filter} setFilter={setFilter} />
|
||||
<ConditionallyRender
|
||||
condition={legalValues.length > 100}
|
||||
show={
|
||||
<ConstraintValueSearch
|
||||
filter={filter}
|
||||
setFilter={setFilter}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{filteredValues.map(match => (
|
||||
<LegalValueLabel
|
||||
key={match.value}
|
||||
|
@ -36,7 +36,15 @@ export const SingleLegalValue = ({
|
||||
<ConstraintFormHeader>
|
||||
Add a single {type.toLowerCase()} value
|
||||
</ConstraintFormHeader>
|
||||
<ConstraintValueSearch filter={filter} setFilter={setFilter} />
|
||||
<ConditionallyRender
|
||||
condition={Boolean(legalValues.length > 100)}
|
||||
show={
|
||||
<ConstraintValueSearch
|
||||
filter={filter}
|
||||
setFilter={setFilter}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={Boolean(legalValues.length)}
|
||||
show={
|
||||
|
@ -4,20 +4,25 @@ import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccord
|
||||
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
|
||||
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
|
||||
import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon';
|
||||
import { Help } from '@mui/icons-material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { dateOperators, DATE_AFTER, IN } from 'constants/operators';
|
||||
import { SAVE } from '../ConstraintAccordionEdit';
|
||||
import {
|
||||
dateOperators,
|
||||
DATE_AFTER,
|
||||
IN,
|
||||
stringOperators,
|
||||
} from 'constants/operators';
|
||||
import { resolveText } from './helpers';
|
||||
import { oneOf } from 'utils/oneOf';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Operator } from 'constants/operators';
|
||||
import { ConstraintOperatorSelect } from 'component/common/ConstraintAccordion/ConstraintOperatorSelect/ConstraintOperatorSelect';
|
||||
import {
|
||||
operatorsForContext,
|
||||
CURRENT_TIME_CONTEXT_FIELD,
|
||||
} 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 {
|
||||
localConstraint: IConstraint;
|
||||
@ -26,6 +31,9 @@ interface IConstraintAccordionViewHeader {
|
||||
setLocalConstraint: React.Dispatch<React.SetStateAction<IConstraint>>;
|
||||
action: string;
|
||||
compact: boolean;
|
||||
onDelete?: () => void;
|
||||
setInvertedOperator: () => void;
|
||||
setCaseInsensitive: () => void;
|
||||
}
|
||||
|
||||
export const ConstraintAccordionEditHeader = ({
|
||||
@ -34,11 +42,15 @@ export const ConstraintAccordionEditHeader = ({
|
||||
setLocalConstraint,
|
||||
setContextName,
|
||||
setOperator,
|
||||
action,
|
||||
onDelete,
|
||||
setInvertedOperator,
|
||||
setCaseInsensitive,
|
||||
}: IConstraintAccordionViewHeader) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const { context } = useUnleashContext();
|
||||
const { contextName, operator } = localConstraint;
|
||||
const [showCaseSensitiveButton, setShowCaseSensitiveButton] =
|
||||
useState(false);
|
||||
|
||||
/* 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
|
||||
@ -60,6 +72,8 @@ export const ConstraintAccordionEditHeader = ({
|
||||
oneOf(dateOperators, operator)
|
||||
) {
|
||||
setOperator(IN);
|
||||
} else if (oneOf(stringOperators, operator)) {
|
||||
setShowCaseSensitiveButton(true);
|
||||
}
|
||||
}, [contextName, setOperator, operator, setLocalConstraint]);
|
||||
|
||||
@ -79,6 +93,9 @@ export const ConstraintAccordionEditHeader = ({
|
||||
value: new Date().toISOString(),
|
||||
}));
|
||||
} else {
|
||||
if (oneOf(stringOperators, operator)) {
|
||||
setShowCaseSensitiveButton(true);
|
||||
}
|
||||
setOperator(operator);
|
||||
}
|
||||
};
|
||||
@ -100,6 +117,10 @@ export const ConstraintAccordionEditHeader = ({
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.bottomSelect}>
|
||||
<InvertedOperatorButton
|
||||
localConstraint={localConstraint}
|
||||
setInvertedOperator={setInvertedOperator}
|
||||
/>
|
||||
<div className={styles.headerSelect}>
|
||||
<ConstraintOperatorSelect
|
||||
options={operatorsForContext(contextName)}
|
||||
@ -107,6 +128,15 @@ export const ConstraintAccordionEditHeader = ({
|
||||
onChange={onOperatorChange}
|
||||
/>
|
||||
</div>
|
||||
<ConditionallyRender
|
||||
condition={showCaseSensitiveButton}
|
||||
show={
|
||||
<CaseSensitiveButton
|
||||
localConstraint={localConstraint}
|
||||
setCaseInsensitive={setCaseInsensitive}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ConditionallyRender
|
||||
@ -117,21 +147,7 @@ export const ConstraintAccordionEditHeader = ({
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
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>
|
||||
<ConstraintAccordionHeaderActions onDelete={onDelete} disableEdit />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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,
|
||||
},
|
||||
}));
|
@ -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>
|
||||
);
|
||||
};
|
@ -6,4 +6,17 @@ export const useStyles = makeStyles()(theme => ({
|
||||
display: 'grid',
|
||||
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',
|
||||
},
|
||||
}));
|
||||
|
@ -8,7 +8,8 @@ import { objectId } from 'utils/objectId';
|
||||
import { useStyles } from './ConstraintAccordionList.styles';
|
||||
import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
|
||||
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 {
|
||||
constraints: IConstraint[];
|
||||
@ -102,11 +103,25 @@ export const ConstraintAccordionList = forwardRef<
|
||||
condition={Boolean(showCreateButton && onAdd)}
|
||||
show={
|
||||
<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
|
||||
type="button"
|
||||
onClick={onAdd}
|
||||
variant="text"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
>
|
||||
Add custom constraint
|
||||
</Button>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Accordion, AccordionSummary, AccordionDetails } from '@mui/material';
|
||||
import { ExpandMore } from '@mui/icons-material';
|
||||
import { IConstraint } from 'interfaces/strategy';
|
||||
|
||||
import { ConstraintAccordionViewBody } from './ConstraintAccordionViewBody/ConstraintAccordionViewBody';
|
||||
@ -12,41 +11,52 @@ import {
|
||||
} from 'constants/operators';
|
||||
|
||||
import { useStyles } from '../ConstraintAccordion.styles';
|
||||
import { useState } from 'react';
|
||||
interface IConstraintAccordionViewProps {
|
||||
constraint: IConstraint;
|
||||
onDelete?: () => void;
|
||||
onEdit?: () => void;
|
||||
compact: boolean;
|
||||
}
|
||||
|
||||
export const ConstraintAccordionView = ({
|
||||
compact,
|
||||
constraint,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}: IConstraintAccordionViewProps) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const [expandable, setExpandable] = useState(true);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
const singleValue = oneOf(
|
||||
[...semVerOperators, ...numOperators, ...dateOperators],
|
||||
constraint.operator
|
||||
);
|
||||
|
||||
const handleClick = () => {
|
||||
if (expandable) {
|
||||
setExpanded(!expanded);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
className={styles.accordion}
|
||||
classes={{ root: styles.accordionRoot }}
|
||||
expanded={expanded}
|
||||
sx={{ cursor: expandable ? 'pointer' : 'default' }}
|
||||
>
|
||||
<AccordionSummary
|
||||
className={styles.summary}
|
||||
expandIcon={<ExpandMore titleAccess="Toggle" />}
|
||||
expandIcon={null}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<ConstraintAccordionViewHeader
|
||||
compact={compact}
|
||||
constraint={constraint}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
singleValue={singleValue}
|
||||
allowExpand={setExpandable}
|
||||
expanded={expanded}
|
||||
/>
|
||||
</AccordionSummary>
|
||||
|
||||
|
@ -1,149 +1,43 @@
|
||||
import { Chip, IconButton, Tooltip, styled } from '@mui/material';
|
||||
import { ConstraintIcon } from 'component/common/ConstraintAccordion/ConstraintIcon';
|
||||
import { Delete, Edit } from '@mui/icons-material';
|
||||
import { IConstraint } from 'interfaces/strategy';
|
||||
|
||||
import { useStyles } from 'component/common/ConstraintAccordion/ConstraintAccordion.styles';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import React from 'react';
|
||||
import { formatConstraintValue } from 'utils/formatConstraintValue';
|
||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||
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),
|
||||
},
|
||||
}));
|
||||
import { ConstraintAccordionViewHeaderInfo } from './ConstraintAccordionViewHeaderInfo/ConstraintAccordionViewHeaderInfo';
|
||||
import { ConstraintAccordionHeaderActions } from '../../ConstraintAccordionHeaderActions/ConstraintAccordionHeaderActions';
|
||||
|
||||
interface IConstraintAccordionViewHeaderProps {
|
||||
compact: boolean;
|
||||
constraint: IConstraint;
|
||||
onDelete?: () => void;
|
||||
onEdit?: () => void;
|
||||
singleValue: boolean;
|
||||
expanded: boolean;
|
||||
allowExpand: (shouldExpand: boolean) => void;
|
||||
}
|
||||
|
||||
export const ConstraintAccordionViewHeader = ({
|
||||
compact,
|
||||
constraint,
|
||||
onEdit,
|
||||
onDelete,
|
||||
singleValue,
|
||||
allowExpand,
|
||||
expanded,
|
||||
}: IConstraintAccordionViewHeaderProps) => {
|
||||
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 (
|
||||
<div className={styles.headerContainer}>
|
||||
<ConstraintIcon />
|
||||
<div className={styles.headerMetaInfo}>
|
||||
<Tooltip title={constraint.contextName} arrow>
|
||||
<StyledHeaderText>
|
||||
{constraint.contextName}
|
||||
</StyledHeaderText>
|
||||
</Tooltip>
|
||||
<div className={styles.headerConstraintContainer}>
|
||||
<ConstraintOperator constraint={constraint} />
|
||||
</div>
|
||||
<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>
|
||||
<ConstraintAccordionViewHeaderInfo
|
||||
constraint={constraint}
|
||||
singleValue={singleValue}
|
||||
allowExpand={allowExpand}
|
||||
expanded={expanded}
|
||||
/>
|
||||
<ConstraintAccordionHeaderActions
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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',
|
||||
}));
|
@ -1,6 +1,7 @@
|
||||
import { IConstraint } from 'interfaces/strategy';
|
||||
import { formatOperatorDescription } from 'component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription';
|
||||
import { useStyles } from 'component/common/ConstraintAccordion/ConstraintOperator/ConstraintOperator.styles';
|
||||
import React from 'react';
|
||||
|
||||
interface IConstraintOperatorProps {
|
||||
constraint: IConstraint;
|
||||
@ -14,15 +15,8 @@ export const ConstraintOperator = ({
|
||||
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>
|
||||
|
13
frontend/src/component/common/ConstraintAccordion/helpers.ts
Normal file
13
frontend/src/component/common/ConstraintAccordion/helpers.ts
Normal 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;
|
||||
}
|
@ -6,7 +6,6 @@ import {
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
||||
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||
import { GuidanceIndicator } from 'component/common/GuidanceIndicator/GuidanceIndicator';
|
||||
|
||||
|
@ -22,7 +22,6 @@ import { FeatureStatusCell } from './FeatureStatusCell/FeatureStatusCell';
|
||||
import { PlaygroundFeatureSchema } from 'hooks/api/actions/usePlayground/playground.model';
|
||||
import { Box, Typography, useMediaQuery, useTheme } from '@mui/material';
|
||||
import useLoading from 'hooks/useLoading';
|
||||
import { GuidanceIndicator } from 'component/common/GuidanceIndicator/GuidanceIndicator';
|
||||
import { VariantCell } from './VariantCell/VariantCell';
|
||||
|
||||
const defaultSort: SortingRule<string> = { id: 'name' };
|
||||
|
@ -32,7 +32,6 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { IUser } from 'interfaces/user';
|
||||
import { IGroup } from 'interfaces/group';
|
||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||
import { mapGroupUsers } from '../../../../hooks/api/getters/useGroup/useGroup';
|
||||
|
||||
const StyledAvatar = styled(Avatar)(({ theme }) => ({
|
||||
width: theme.spacing(4),
|
||||
|
@ -1,11 +1,10 @@
|
||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import { IProjectRole } from 'interfaces/role';
|
||||
import { IGroup } from 'interfaces/group';
|
||||
import { IUser } from 'interfaces/user';
|
||||
import { useGroups } from '../useGroups/useGroups';
|
||||
import { mapGroupUsers } from '../useGroup/useGroup';
|
||||
|
||||
export enum ENTITY_TYPE {
|
||||
|
@ -95,6 +95,8 @@ export default createTheme({
|
||||
light: colors.grey[200],
|
||||
main: colors.grey[400],
|
||||
dark: colors.grey[600],
|
||||
background: 'white',
|
||||
contrast: colors.grey[300],
|
||||
},
|
||||
divider: colors.grey[300],
|
||||
dividerAlternative: colors.grey[400],
|
||||
|
@ -88,6 +88,8 @@ declare module '@mui/material/styles' {
|
||||
main: string;
|
||||
light: string;
|
||||
dark: string;
|
||||
background: string;
|
||||
contrast: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user