diff --git a/frontend/src/assets/icons/case-sensitive.svg b/frontend/src/assets/icons/case-sensitive.svg
new file mode 100644
index 0000000000..1383f16794
--- /dev/null
+++ b/frontend/src/assets/icons/case-sensitive.svg
@@ -0,0 +1 @@
+
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription.ts b/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription.ts
index 845d2d14bb..5e01186ad6 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription.ts
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription.ts
@@ -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',
+};
diff --git a/frontend/src/component/common/ConstraintsList/ConstraintItemHeader/ConstraintItemHeader.tsx b/frontend/src/component/common/ConstraintsList/ConstraintItemHeader/ConstraintItemHeader.tsx
index bcaf83b751..880de40317 100644
--- a/frontend/src/component/common/ConstraintsList/ConstraintItemHeader/ConstraintItemHeader.tsx
+++ b/frontend/src/component/common/ConstraintsList/ConstraintItemHeader/ConstraintItemHeader.tsx
@@ -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 = () => (
-
-
+const Operator: FC<{
+ label: ConstraintSchema['operator'];
+ inverted?: boolean;
+}> = ({ label, inverted }) => (
+
+
);
-const Operator: FC<{ label: ConstraintSchema['operator'] }> = ({ label }) => (
-
-
-
+const StrategyEvalChipLessInlinePadding = styled(StrategyEvaluationChip)(
+ ({ theme }) => ({
+ '> span': {
+ paddingInline: theme.spacing(0.5),
+ },
+ }),
);
-const CaseInsensitive: FC = () => (
-
- Aa} />
-
-);
+const CaseSensitive: FC = () => {
+ return (
+
+
+ }
+ />
+
+ );
+};
const StyledOperatorGroup = styled('div')(({ theme }) => ({
display: 'flex',
@@ -53,9 +73,10 @@ export const ConstraintItemHeader: FC<
>
{contextName}
- {inverted ? : null}
-
- {caseInsensitive ? : null}
+
+ {isCaseSensitive(operator, caseInsensitive) ? (
+
+ ) : null}
);
diff --git a/frontend/src/component/common/ConstraintsList/ConstraintItemHeader/isCaseSensitive.test.ts b/frontend/src/component/common/ConstraintsList/ConstraintItemHeader/isCaseSensitive.test.ts
new file mode 100644
index 0000000000..70578bd2a2
--- /dev/null
+++ b/frontend/src/component/common/ConstraintsList/ConstraintItemHeader/isCaseSensitive.test.ts
@@ -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);
+ },
+);
diff --git a/frontend/src/component/common/ConstraintsList/ConstraintItemHeader/isCaseSensitive.ts b/frontend/src/component/common/ConstraintsList/ConstraintItemHeader/isCaseSensitive.ts
new file mode 100644
index 0000000000..d9dfbf0ae1
--- /dev/null
+++ b/frontend/src/component/common/ConstraintsList/ConstraintItemHeader/isCaseSensitive.ts
@@ -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);