1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-23 01:16:27 +02:00

feat: update UI to add hints about implicit ^ and $ (#4667)

This PR updates the UI to reflect the changes to the implicit ^ and $
that we now add. The changes are:

1. Show input adornments for ^ and $ when you create a pattern.
2. Mention that ^ and $ are added implicitly in description.
3. Checks the example you provide against the pattern with added ^ and $
+ adds a test for that.

Points 1 and 2:

![image](https://github.com/Unleash/unleash/assets/17786332/88c610b4-444b-4a83-a50a-4b7639614a86)


## Discussion point:

I have not touched the information about the pattern yet as the PR that
updates that is still in review (#4656), but it would be prudent to also
update that info to make it clearer. I can address that in a follow-up
PR.
This commit is contained in:
Thomas Heartman 2023-09-13 08:22:55 +02:00 committed by GitHub
parent 03d6ed0c32
commit f49cc8cd33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 51 additions and 22 deletions

View File

@ -37,7 +37,7 @@ export const FeatureNamingPatternInfo: React.FC<Props> = ({
<dl id="feature-naming-pattern-info"> <dl id="feature-naming-pattern-info">
<dt>Pattern</dt> <dt>Pattern</dt>
<dd> <dd>
<code>{featureNaming.pattern}</code> <code>^{featureNaming.pattern}$</code>
</dd> </dd>
<ConditionallyRender <ConditionallyRender
condition={Boolean(featureNaming?.example)} condition={Boolean(featureNaming?.example)}

View File

@ -4,7 +4,6 @@ import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
export const FeatureFlagNamingTooltip: FC = () => { export const FeatureFlagNamingTooltip: FC = () => {
const X = 'X'; const X = 'X';
const Y = 'Y';
const nx = 'n{X,}'; const nx = 'n{X,}';
const nxy = 'n{X,Y}'; const nxy = 'n{X,Y}';
return ( return (
@ -14,7 +13,7 @@ export const FeatureFlagNamingTooltip: FC = () => {
<Box> <Box>
<h3>Enforce a naming convention for feature flags</h3> <h3>Enforce a naming convention for feature flags</h3>
<hr /> <hr />
<p>{`eg. ^[A-Za-z0-9]{2}[.][a-z]{4,12}$ matches 'a1.project'`}</p> <p>{`eg. [A-Za-z0-9]{2}[.][a-z]{4,12} matches 'a1.project'`}</p>
<div className="scrollable"> <div className="scrollable">
<h3>Brackets:</h3> <h3>Brackets:</h3>
<table> <table>

View File

@ -4,7 +4,7 @@ import { StickinessSelect } from 'component/feature/StrategyTypes/FlexibleStrate
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import Select from 'component/common/select'; import Select from 'component/common/select';
import { ProjectMode } from '../hooks/useProjectForm'; import { ProjectMode } from '../hooks/useProjectForm';
import { Box, styled, TextField } from '@mui/material'; import { Box, InputAdornment, styled, TextField } from '@mui/material';
import { CollaborationModeTooltip } from './CollaborationModeTooltip'; import { CollaborationModeTooltip } from './CollaborationModeTooltip';
import Input from 'component/common/Input/Input'; import Input from 'component/common/Input/Input';
import { FeatureTogglesLimitTooltip } from './FeatureTogglesLimitTooltip'; import { FeatureTogglesLimitTooltip } from './FeatureTogglesLimitTooltip';
@ -106,6 +106,10 @@ const StyledFlagNamingContainer = styled('div')(({ theme }) => ({
'& > *': { width: '100%' }, '& > *': { width: '100%' },
})); }));
const StyledPatternNamingExplanation = styled('div')(({ theme }) => ({
'p + p': { marginTop: theme.spacing(1) },
}));
export const validateFeatureNamingExample = ({ export const validateFeatureNamingExample = ({
pattern, pattern,
example, example,
@ -118,7 +122,7 @@ export const validateFeatureNamingExample = ({
if (featureNamingPatternError || !example || !pattern) { if (featureNamingPatternError || !example || !pattern) {
return { state: 'valid' }; return { state: 'valid' };
} else if (example && pattern) { } else if (example && pattern) {
const regex = new RegExp(pattern); const regex = new RegExp(`^${pattern}$`);
const matches = regex.test(example); const matches = regex.test(example);
if (!matches) { if (!matches) {
return { state: 'invalid', reason: 'Example does not match regex' }; return { state: 'invalid', reason: 'Example does not match regex' };
@ -354,8 +358,9 @@ const ProjectForm: React.FC<IProjectForm> = ({
<FeatureFlagNamingTooltip /> <FeatureFlagNamingTooltip />
</Box> </Box>
<StyledSubtitle> <StyledSubtitle>
<p id="pattern-naming-description"> <StyledPatternNamingExplanation id="pattern-naming-description">
A feature flag naming pattern is a{' '} <p>
Define a{' '}
<a <a
href={`https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions`} href={`https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions`}
target="_blank" target="_blank"
@ -363,20 +368,35 @@ const ProjectForm: React.FC<IProjectForm> = ({
> >
JavaScript RegEx JavaScript RegEx
</a>{' '} </a>{' '}
used to enforce feature flag names within used to enforce feature flag names
this project. within this project. The regex will be
surrounded by a leading <code>^</code>{' '}
and a trailing <code>$</code>.
</p> </p>
<p> <p>
Leave it empty if you dont want to add a Leave it empty if you dont want to add
naming pattern. a naming pattern.
</p> </p>
</StyledPatternNamingExplanation>
</StyledSubtitle> </StyledSubtitle>
<StyledFlagNamingContainer> <StyledFlagNamingContainer>
<StyledInput <StyledInput
label={'Naming Pattern'} label={'Naming Pattern'}
name="feature flag naming pattern" name="feature flag naming pattern"
aria-describedby="pattern-naming-description" aria-describedby="pattern-naming-description"
placeholder="^[A-Za-z]+\.[A-Za-z]+\.[A-Za-z0-9-]+$" placeholder="[A-Za-z]+\.[A-Za-z]+\.[A-Za-z0-9-]+"
InputProps={{
startAdornment: (
<InputAdornment position="start">
^
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
$
</InputAdornment>
),
}}
type={'text'} type={'text'}
value={featureNamingPattern || ''} value={featureNamingPattern || ''}
error={Boolean(errors.featureNamingPattern)} error={Boolean(errors.featureNamingPattern)}

View File

@ -47,4 +47,14 @@ describe('validateFeatureNaming', () => {
expect(result.state).toBe(state); expect(result.state).toBe(state);
} }
); );
test('the pattern gets an implicit leading ^ and trailing $ added', () => {
const result = validateFeatureNamingExample({
pattern: '[a-z]+',
example: 'not.valid',
featureNamingPatternError: undefined,
});
expect(result.state).toBe('invalid');
});
}); });