mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	chore(1-3431): rework constraint equality and case sensitivity (#9591)
This commit is contained in:
		
							parent
							
								
									03699c8e80
								
							
						
					
					
						commit
						19d2a553f0
					
				
							
								
								
									
										1
									
								
								frontend/src/assets/icons/case-sensitive.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								frontend/src/assets/icons/case-sensitive.svg
									
									
									
									
									
										Normal 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 | 
| @ -1,6 +1,12 @@ | |||||||
| import type { Operator } from 'constants/operators'; | 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]; |     return constraintOperatorDescriptions[operator]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -21,3 +27,21 @@ const constraintOperatorDescriptions = { | |||||||
|     SEMVER_GT: 'is a SemVer greater than', |     SEMVER_GT: 'is a SemVer greater than', | ||||||
|     SEMVER_LT: 'is a SemVer less 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', | ||||||
|  | }; | ||||||
|  | |||||||
| @ -7,24 +7,44 @@ import type { ConstraintSchema } from 'openapi'; | |||||||
| import { formatOperatorDescription } from 'component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription'; | import { formatOperatorDescription } from 'component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription'; | ||||||
| import { StrategyEvaluationChip } from '../StrategyEvaluationChip/StrategyEvaluationChip'; | import { StrategyEvaluationChip } from '../StrategyEvaluationChip/StrategyEvaluationChip'; | ||||||
| import { styled, Tooltip } from '@mui/material'; | 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<{ | ||||||
|     <Tooltip title='NOT (operator is negated)' arrow> |     label: ConstraintSchema['operator']; | ||||||
|         <StrategyEvaluationChip label='≠' /> |     inverted?: boolean; | ||||||
|  | }> = ({ label, inverted }) => ( | ||||||
|  |     <Tooltip title={inverted ? `Not ${label}` : label} arrow> | ||||||
|  |         <StrategyEvaluationChip | ||||||
|  |             label={formatOperatorDescription(label, inverted)} | ||||||
|  |         /> | ||||||
|     </Tooltip> |     </Tooltip> | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| const Operator: FC<{ label: ConstraintSchema['operator'] }> = ({ label }) => ( | const StrategyEvalChipLessInlinePadding = styled(StrategyEvaluationChip)( | ||||||
|     <Tooltip title={label} arrow> |     ({ theme }) => ({ | ||||||
|         <StrategyEvaluationChip label={formatOperatorDescription(label)} /> |         '> span': { | ||||||
|     </Tooltip> |             paddingInline: theme.spacing(0.5), | ||||||
|  |         }, | ||||||
|  |     }), | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| const CaseInsensitive: FC = () => ( | const CaseSensitive: FC = () => { | ||||||
|     <Tooltip title='Case sensitive' arrow> |     return ( | ||||||
|         <StrategyEvaluationChip label={<s>Aa</s>} /> |         <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> |         </Tooltip> | ||||||
| ); |     ); | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| const StyledOperatorGroup = styled('div')(({ theme }) => ({ | const StyledOperatorGroup = styled('div')(({ theme }) => ({ | ||||||
|     display: 'flex', |     display: 'flex', | ||||||
| @ -53,9 +73,10 @@ export const ConstraintItemHeader: FC< | |||||||
|         > |         > | ||||||
|             {contextName} |             {contextName} | ||||||
|             <StyledOperatorGroup> |             <StyledOperatorGroup> | ||||||
|                 {inverted ? <Inverted /> : null} |                 <Operator label={operator} inverted={inverted} /> | ||||||
|                 <Operator label={operator} /> |                 {isCaseSensitive(operator, caseInsensitive) ? ( | ||||||
|                 {caseInsensitive ? <CaseInsensitive /> : null} |                     <CaseSensitive /> | ||||||
|  |                 ) : null} | ||||||
|             </StyledOperatorGroup> |             </StyledOperatorGroup> | ||||||
|         </StrategyEvaluationItem> |         </StrategyEvaluationItem> | ||||||
|     ); |     ); | ||||||
|  | |||||||
| @ -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); | ||||||
|  |     }, | ||||||
|  | ); | ||||||
| @ -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); | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user