1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +02:00

chore(1-3431): rework constraint equality and case sensitivity (#9591)

This commit is contained in:
Thomas Heartman 2025-03-21 15:26:05 +01:00 committed by GitHub
parent 03699c8e80
commit 19d2a553f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 119 additions and 16 deletions

View File

@ -0,0 +1 @@
<svg width="17" height="11" fill="#000"><path d="m2.602 7.45-.725 2.025a.696.696 0 0 1-.275.388.776.776 0 0 1-.45.137.788.788 0 0 1-.675-.35.76.76 0 0 1-.1-.75L3.527.55a.91.91 0 0 1 .3-.4.77.77 0 0 1 .475-.15h.65a.77.77 0 0 1 .475.15.91.91 0 0 1 .3.4l3.15 8.375c.1.267.07.512-.088.737a.765.765 0 0 1-.662.338.77.77 0 0 1-.475-.15.91.91 0 0 1-.3-.4l-.7-2h-4.05ZM3.127 6h3l-1.45-4.15h-.1L3.127 6Zm9.225 4.275c-.817 0-1.459-.213-1.925-.638-.467-.425-.7-1.004-.7-1.737 0-.7.27-1.27.812-1.713.542-.441 1.238-.662 2.088-.662.383 0 .737.03 1.062.087.325.059.604.155.838.288v-.35c0-.45-.155-.808-.463-1.075-.308-.267-.73-.4-1.262-.4a2.238 2.238 0 0 0-1.325.425.938.938 0 0 1-.5.2.687.687 0 0 1-.5-.15.658.658 0 0 1-.263-.438.521.521 0 0 1 .163-.462c.316-.283.675-.5 1.075-.65a3.9 3.9 0 0 1 1.375-.225c1.033 0 1.825.246 2.375.738.55.491.825 1.204.825 2.137v3.8c0 .2-.071.37-.213.513a.698.698 0 0 1-.512.212.72.72 0 0 1-.525-.225.72.72 0 0 1-.225-.525V9.2h-.075a2.285 2.285 0 0 1-.875.788 2.658 2.658 0 0 1-1.25.287Zm.25-1.25c.533 0 .987-.188 1.362-.563.375-.374.563-.829.563-1.362a2.849 2.849 0 0 0-.8-.3c-.3-.067-.575-.1-.825-.1-.534 0-.942.104-1.225.313-.284.208-.425.504-.425.887 0 .333.125.604.375.813.25.208.575.312.975.312Z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,6 +1,12 @@
import type { Operator } from 'constants/operators';
export const formatOperatorDescription = (operator: Operator): string => {
export const formatOperatorDescription = (
operator: Operator,
inverted?: boolean,
): string => {
if (inverted) {
return invertedConstraintOperatorDescriptions[operator];
}
return constraintOperatorDescriptions[operator];
};
@ -21,3 +27,21 @@ const constraintOperatorDescriptions = {
SEMVER_GT: 'is a SemVer greater than',
SEMVER_LT: 'is a SemVer less than',
};
const invertedConstraintOperatorDescriptions = {
IN: 'is not one of',
NOT_IN: 'is one of',
STR_CONTAINS: 'is a string that does not contain',
STR_STARTS_WITH: 'is a string that does not start with',
STR_ENDS_WITH: 'is a string that does not end with',
NUM_EQ: 'is a number not equal to',
NUM_GT: 'is a number not greater than',
NUM_GTE: 'is a number less than',
NUM_LT: 'is a number not less than',
NUM_LTE: 'is a number greater than',
DATE_BEFORE: 'is a date not before',
DATE_AFTER: 'is a date not after',
SEMVER_EQ: 'is a SemVer not equal to',
SEMVER_GT: 'is a SemVer not greater than',
SEMVER_LT: 'is a SemVer not less than',
};

View File

@ -7,24 +7,44 @@ import type { ConstraintSchema } from 'openapi';
import { formatOperatorDescription } from 'component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription';
import { StrategyEvaluationChip } from '../StrategyEvaluationChip/StrategyEvaluationChip';
import { styled, Tooltip } from '@mui/material';
import { ReactComponent as CaseSensitiveIcon } from 'assets/icons/case-sensitive.svg';
import { isCaseSensitive } from './isCaseSensitive';
const Inverted: FC = () => (
<Tooltip title='NOT (operator is negated)' arrow>
<StrategyEvaluationChip label='≠' />
const Operator: FC<{
label: ConstraintSchema['operator'];
inverted?: boolean;
}> = ({ label, inverted }) => (
<Tooltip title={inverted ? `Not ${label}` : label} arrow>
<StrategyEvaluationChip
label={formatOperatorDescription(label, inverted)}
/>
</Tooltip>
);
const Operator: FC<{ label: ConstraintSchema['operator'] }> = ({ label }) => (
<Tooltip title={label} arrow>
<StrategyEvaluationChip label={formatOperatorDescription(label)} />
</Tooltip>
const StrategyEvalChipLessInlinePadding = styled(StrategyEvaluationChip)(
({ theme }) => ({
'> span': {
paddingInline: theme.spacing(0.5),
},
}),
);
const CaseInsensitive: FC = () => (
<Tooltip title='Case sensitive' arrow>
<StrategyEvaluationChip label={<s>Aa</s>} />
</Tooltip>
);
const CaseSensitive: FC = () => {
return (
<Tooltip title='The match is case sensitive' arrow>
<StrategyEvalChipLessInlinePadding
aria-label='The match is case sensitive'
label={
<CaseSensitiveIcon
style={{ verticalAlign: 'middle' }}
fill='currentColor'
aria-hidden={true}
/>
}
/>
</Tooltip>
);
};
const StyledOperatorGroup = styled('div')(({ theme }) => ({
display: 'flex',
@ -53,9 +73,10 @@ export const ConstraintItemHeader: FC<
>
{contextName}
<StyledOperatorGroup>
{inverted ? <Inverted /> : null}
<Operator label={operator} />
{caseInsensitive ? <CaseInsensitive /> : null}
<Operator label={operator} inverted={inverted} />
{isCaseSensitive(operator, caseInsensitive) ? (
<CaseSensitive />
) : null}
</StyledOperatorGroup>
</StrategyEvaluationItem>
);

View File

@ -0,0 +1,45 @@
import {
allOperators,
inOperators,
stringOperators,
} from 'constants/operators';
import { isCaseSensitive } from './isCaseSensitive';
test('`IN` and `NOT_IN` are always case sensitive', () => {
expect(
inOperators
.flatMap((operator) =>
[true, false, undefined].map((caseInsensitive) =>
isCaseSensitive(operator, caseInsensitive),
),
)
.every((result) => result === true),
).toBe(true);
});
test('If `caseInsensitive` is true, all operators except for `IN` and `NOT_IN` are considered case insensitive', () => {
expect(
allOperators
.filter((operator) => !inOperators.includes(operator))
.map((operator) => isCaseSensitive(operator, true))
.every((result) => result === false),
).toBe(true);
});
test.each([false, undefined])(
'If `caseInsensitive` is %s, only string (and in) operators are considered case sensitive',
(caseInsensitive) => {
const stringResults = stringOperators.map((operator) =>
isCaseSensitive(operator, caseInsensitive),
);
const nonStringResults = allOperators
.filter(
(operator) =>
![...stringOperators, ...inOperators].includes(operator),
)
.map((operator) => isCaseSensitive(operator, caseInsensitive));
expect(stringResults.every((result) => result === true)).toBe(true);
expect(nonStringResults.every((result) => result === false)).toBe(true);
},
);

View File

@ -0,0 +1,12 @@
import {
inOperators,
stringOperators,
type Operator,
} from 'constants/operators';
export const isCaseSensitive = (
operator: Operator,
caseInsensitive?: boolean,
) =>
inOperators.includes(operator) ||
(stringOperators.includes(operator) && !caseInsensitive);