mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
1-1319: add feature naming pattern descriptions (#4612)
This PR adds a feature naming pattern description to the project form. It's rendered as a multi-line input field. The description is also stored in the db. This adapts most of @andreas-unleash's PR #4599 with some minor changes (using description instead of prompt). Actually displaying this data to the users will come in a later PR. ![image](https://github.com/Unleash/unleash/assets/17786332/b96d2dbb-2b90-4adf-bc83-cdc534c507ea)
This commit is contained in:
parent
31df85a3f5
commit
73b7cc0b5a
@ -35,8 +35,10 @@ const CreateProject = () => {
|
|||||||
featureLimit,
|
featureLimit,
|
||||||
featureNamingPattern,
|
featureNamingPattern,
|
||||||
featureNamingExample,
|
featureNamingExample,
|
||||||
|
featureNamingDescription,
|
||||||
setFeatureNamingExample,
|
setFeatureNamingExample,
|
||||||
setFeatureNamingPattern,
|
setFeatureNamingPattern,
|
||||||
|
setFeatureNamingDescription,
|
||||||
setProjectId,
|
setProjectId,
|
||||||
setProjectName,
|
setProjectName,
|
||||||
setProjectDesc,
|
setProjectDesc,
|
||||||
@ -114,7 +116,9 @@ const CreateProject = () => {
|
|||||||
featureLimit={featureLimit}
|
featureLimit={featureLimit}
|
||||||
featureNamingExample={featureNamingExample}
|
featureNamingExample={featureNamingExample}
|
||||||
featureNamingPattern={featureNamingPattern}
|
featureNamingPattern={featureNamingPattern}
|
||||||
setProjectNamingPattern={setFeatureNamingPattern}
|
setFeatureNamingPattern={setFeatureNamingPattern}
|
||||||
|
featureNamingDescription={featureNamingDescription}
|
||||||
|
setFeatureNamingDescription={setFeatureNamingDescription}
|
||||||
setFeatureNamingExample={setFeatureNamingExample}
|
setFeatureNamingExample={setFeatureNamingExample}
|
||||||
setProjectStickiness={setProjectStickiness}
|
setProjectStickiness={setProjectStickiness}
|
||||||
setFeatureLimit={setFeatureLimit}
|
setFeatureLimit={setFeatureLimit}
|
||||||
|
@ -43,6 +43,7 @@ const EditProject = () => {
|
|||||||
projectMode,
|
projectMode,
|
||||||
featureNamingPattern,
|
featureNamingPattern,
|
||||||
featureNamingExample,
|
featureNamingExample,
|
||||||
|
featureNamingDescription,
|
||||||
setProjectId,
|
setProjectId,
|
||||||
setProjectName,
|
setProjectName,
|
||||||
setProjectDesc,
|
setProjectDesc,
|
||||||
@ -50,6 +51,7 @@ const EditProject = () => {
|
|||||||
setProjectMode,
|
setProjectMode,
|
||||||
setFeatureNamingExample,
|
setFeatureNamingExample,
|
||||||
setFeatureNamingPattern,
|
setFeatureNamingPattern,
|
||||||
|
setFeatureNamingDescription,
|
||||||
getProjectPayload,
|
getProjectPayload,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
validateProjectId,
|
validateProjectId,
|
||||||
@ -63,7 +65,8 @@ const EditProject = () => {
|
|||||||
project.mode,
|
project.mode,
|
||||||
String(project.featureLimit),
|
String(project.featureLimit),
|
||||||
project?.featureNaming?.pattern || '',
|
project?.featureNaming?.pattern || '',
|
||||||
project?.featureNaming?.example || ''
|
project?.featureNaming?.example || '',
|
||||||
|
project?.featureNaming?.description || ''
|
||||||
);
|
);
|
||||||
|
|
||||||
const formatApiCode = () => {
|
const formatApiCode = () => {
|
||||||
@ -131,13 +134,15 @@ const EditProject = () => {
|
|||||||
projectMode={projectMode}
|
projectMode={projectMode}
|
||||||
featureNamingPattern={featureNamingPattern}
|
featureNamingPattern={featureNamingPattern}
|
||||||
featureNamingExample={featureNamingExample}
|
featureNamingExample={featureNamingExample}
|
||||||
|
featureNamingDescription={featureNamingDescription}
|
||||||
setProjectName={setProjectName}
|
setProjectName={setProjectName}
|
||||||
projectStickiness={projectStickiness}
|
projectStickiness={projectStickiness}
|
||||||
setProjectStickiness={setProjectStickiness}
|
setProjectStickiness={setProjectStickiness}
|
||||||
setProjectMode={setProjectMode}
|
setProjectMode={setProjectMode}
|
||||||
setFeatureLimit={() => {}}
|
setFeatureLimit={() => {}}
|
||||||
setFeatureNamingExample={setFeatureNamingExample}
|
setFeatureNamingExample={setFeatureNamingExample}
|
||||||
setProjectNamingPattern={setFeatureNamingPattern}
|
setFeatureNamingPattern={setFeatureNamingPattern}
|
||||||
|
setFeatureNamingDescription={setFeatureNamingDescription}
|
||||||
featureLimit={''}
|
featureLimit={''}
|
||||||
projectDesc={projectDesc}
|
projectDesc={projectDesc}
|
||||||
setProjectDesc={setProjectDesc}
|
setProjectDesc={setProjectDesc}
|
||||||
|
@ -21,8 +21,10 @@ interface IProjectForm {
|
|||||||
featureCount?: number;
|
featureCount?: number;
|
||||||
featureNamingPattern?: string;
|
featureNamingPattern?: string;
|
||||||
featureNamingExample?: string;
|
featureNamingExample?: string;
|
||||||
setProjectNamingPattern?: React.Dispatch<React.SetStateAction<string>>;
|
featureNamingDescription?: string;
|
||||||
|
setFeatureNamingPattern?: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setFeatureNamingExample?: React.Dispatch<React.SetStateAction<string>>;
|
setFeatureNamingExample?: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
setFeatureNamingDescription?: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setProjectStickiness?: React.Dispatch<React.SetStateAction<string>>;
|
setProjectStickiness?: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setProjectMode?: React.Dispatch<React.SetStateAction<ProjectMode>>;
|
setProjectMode?: React.Dispatch<React.SetStateAction<ProjectMode>>;
|
||||||
setProjectId: React.Dispatch<React.SetStateAction<string>>;
|
setProjectId: React.Dispatch<React.SetStateAction<string>>;
|
||||||
@ -100,7 +102,7 @@ const StyledFlagNamingContainer = styled('div')(({ theme }) => ({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
mt: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
'& > *': { width: '100%' },
|
'& > *': { width: '100%' },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -116,8 +118,10 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
featureCount,
|
featureCount,
|
||||||
featureNamingExample,
|
featureNamingExample,
|
||||||
featureNamingPattern,
|
featureNamingPattern,
|
||||||
|
featureNamingDescription,
|
||||||
setFeatureNamingExample,
|
setFeatureNamingExample,
|
||||||
setProjectNamingPattern,
|
setFeatureNamingPattern,
|
||||||
|
setFeatureNamingDescription,
|
||||||
setProjectId,
|
setProjectId,
|
||||||
setProjectName,
|
setProjectName,
|
||||||
setProjectDesc,
|
setProjectDesc,
|
||||||
@ -134,11 +138,11 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
const onSetFeatureNamingPattern = (regex: string) => {
|
const onSetFeatureNamingPattern = (regex: string) => {
|
||||||
try {
|
try {
|
||||||
new RegExp(regex);
|
new RegExp(regex);
|
||||||
setProjectNamingPattern && setProjectNamingPattern(regex);
|
setFeatureNamingPattern && setFeatureNamingPattern(regex);
|
||||||
clearErrors();
|
clearErrors();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errors.featureNamingPattern = 'Invalid regular expression';
|
errors.featureNamingPattern = 'Invalid regular expression';
|
||||||
setProjectNamingPattern && setProjectNamingPattern(regex);
|
setFeatureNamingPattern && setFeatureNamingPattern(regex);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -151,10 +155,14 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
} else {
|
} else {
|
||||||
delete errors.namingExample;
|
delete errors.namingExample;
|
||||||
}
|
}
|
||||||
setFeatureNamingExample && setFeatureNamingExample(trim(example));
|
setFeatureNamingExample && setFeatureNamingExample(example);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSetFeatureNamingDescription = (description: string) => {
|
||||||
|
setFeatureNamingDescription && setFeatureNamingDescription(description);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledForm onSubmit={handleSubmit}>
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
@ -283,11 +291,7 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
</StyledInputContainer>
|
</StyledInputContainer>
|
||||||
</>
|
</>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={
|
condition={Boolean(shouldShowFlagNaming)}
|
||||||
Boolean(shouldShowFlagNaming) &&
|
|
||||||
setProjectNamingPattern != null &&
|
|
||||||
setFeatureNamingExample != null
|
|
||||||
}
|
|
||||||
show={
|
show={
|
||||||
<StyledFieldset>
|
<StyledFieldset>
|
||||||
<Box
|
<Box
|
||||||
@ -322,9 +326,9 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
<StyledFlagNamingContainer>
|
<StyledFlagNamingContainer>
|
||||||
<StyledInput
|
<StyledInput
|
||||||
label={'Naming Pattern'}
|
label={'Naming Pattern'}
|
||||||
name="pattern"
|
name="feature flag naming pattern"
|
||||||
aria-describedby="pattern-naming-description"
|
aria-describedby="pattern-naming-description"
|
||||||
placeholder="^[A-Za-z]+-[A-Za-z0-9]+$"
|
placeholder="^[A-Za-z]+\.[A-Za-z]+\.[A-Za-z0-9-]+$"
|
||||||
type={'text'}
|
type={'text'}
|
||||||
value={featureNamingPattern || ''}
|
value={featureNamingPattern || ''}
|
||||||
error={Boolean(errors.featureNamingPattern)}
|
error={Boolean(errors.featureNamingPattern)}
|
||||||
@ -337,20 +341,20 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StyledSubtitle>
|
<StyledSubtitle>
|
||||||
<p id="pattern-example-description">
|
<p id="pattern-additional-description">
|
||||||
The example will be shown to users when
|
The example and description will be
|
||||||
they create a new feature flag in this
|
shown to users when they create a new
|
||||||
project.
|
feature flag in this project.
|
||||||
</p>
|
</p>
|
||||||
</StyledSubtitle>
|
</StyledSubtitle>
|
||||||
|
|
||||||
<StyledInput
|
<StyledInput
|
||||||
label={'Naming Example'}
|
label={'Naming Example'}
|
||||||
name="example"
|
name="feature flag naming example"
|
||||||
type={'text'}
|
type={'text'}
|
||||||
aria-describedBy="pattern-example-description"
|
aria-describedBy="pattern-additional-description"
|
||||||
value={featureNamingExample || ''}
|
value={featureNamingExample || ''}
|
||||||
placeholder="dx-feature1"
|
placeholder="dx.feature1.1-135"
|
||||||
error={Boolean(errors.namingExample)}
|
error={Boolean(errors.namingExample)}
|
||||||
errorText={errors.namingExample}
|
errorText={errors.namingExample}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
@ -359,6 +363,23 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<StyledTextField
|
||||||
|
label={'Naming pattern description'}
|
||||||
|
name="feature flag naming description"
|
||||||
|
type={'text'}
|
||||||
|
aria-describedBy="pattern-additional-description"
|
||||||
|
placeholder={`<project>.<featureName>.<ticket>
|
||||||
|
|
||||||
|
The flag name should contain the project name, the feature name, and the ticket number, each separated by a dot.`}
|
||||||
|
multiline
|
||||||
|
minRows={5}
|
||||||
|
value={featureNamingDescription || ''}
|
||||||
|
onChange={e =>
|
||||||
|
onSetFeatureNamingDescription(
|
||||||
|
e.target.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</StyledFlagNamingContainer>
|
</StyledFlagNamingContainer>
|
||||||
</StyledFieldset>
|
</StyledFieldset>
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ const EditProject = () => {
|
|||||||
featureLimit,
|
featureLimit,
|
||||||
featureNamingPattern,
|
featureNamingPattern,
|
||||||
featureNamingExample,
|
featureNamingExample,
|
||||||
|
featureNamingDescription,
|
||||||
setProjectId,
|
setProjectId,
|
||||||
setProjectName,
|
setProjectName,
|
||||||
setProjectDesc,
|
setProjectDesc,
|
||||||
@ -46,6 +47,7 @@ const EditProject = () => {
|
|||||||
setFeatureLimit,
|
setFeatureLimit,
|
||||||
setFeatureNamingPattern,
|
setFeatureNamingPattern,
|
||||||
setFeatureNamingExample,
|
setFeatureNamingExample,
|
||||||
|
setFeatureNamingDescription,
|
||||||
getProjectPayload,
|
getProjectPayload,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
validateProjectId,
|
validateProjectId,
|
||||||
@ -59,7 +61,8 @@ const EditProject = () => {
|
|||||||
project.mode,
|
project.mode,
|
||||||
project.featureLimit ? String(project.featureLimit) : '',
|
project.featureLimit ? String(project.featureLimit) : '',
|
||||||
project.featureNaming?.pattern || '',
|
project.featureNaming?.pattern || '',
|
||||||
project.featureNaming?.example || ''
|
project.featureNaming?.example || '',
|
||||||
|
project.featureNaming?.description || ''
|
||||||
);
|
);
|
||||||
|
|
||||||
const formatApiCode = () => {
|
const formatApiCode = () => {
|
||||||
@ -123,12 +126,14 @@ const EditProject = () => {
|
|||||||
featureCount={project.features.length}
|
featureCount={project.features.length}
|
||||||
featureNamingPattern={featureNamingPattern}
|
featureNamingPattern={featureNamingPattern}
|
||||||
featureNamingExample={featureNamingExample}
|
featureNamingExample={featureNamingExample}
|
||||||
|
featureNamingDescription={featureNamingDescription}
|
||||||
setProjectName={setProjectName}
|
setProjectName={setProjectName}
|
||||||
projectStickiness={projectStickiness}
|
projectStickiness={projectStickiness}
|
||||||
setProjectStickiness={setProjectStickiness}
|
setProjectStickiness={setProjectStickiness}
|
||||||
setProjectMode={setProjectMode}
|
setProjectMode={setProjectMode}
|
||||||
setProjectNamingPattern={setFeatureNamingPattern}
|
setFeatureNamingPattern={setFeatureNamingPattern}
|
||||||
setFeatureNamingExample={setFeatureNamingExample}
|
setFeatureNamingExample={setFeatureNamingExample}
|
||||||
|
setFeatureNamingDescription={setFeatureNamingDescription}
|
||||||
projectDesc={projectDesc}
|
projectDesc={projectDesc}
|
||||||
mode="Edit"
|
mode="Edit"
|
||||||
setProjectDesc={setProjectDesc}
|
setProjectDesc={setProjectDesc}
|
||||||
|
@ -12,7 +12,8 @@ const useProjectForm = (
|
|||||||
initialProjectMode: ProjectMode = 'open',
|
initialProjectMode: ProjectMode = 'open',
|
||||||
initialFeatureLimit = '',
|
initialFeatureLimit = '',
|
||||||
initialFeatureNamingPattern = '',
|
initialFeatureNamingPattern = '',
|
||||||
initialFeatureNamingExample = ''
|
initialFeatureNamingExample = '',
|
||||||
|
initialFeatureNamingDescription = ''
|
||||||
) => {
|
) => {
|
||||||
const [projectId, setProjectId] = useState(initialProjectId);
|
const [projectId, setProjectId] = useState(initialProjectId);
|
||||||
|
|
||||||
@ -31,6 +32,11 @@ const useProjectForm = (
|
|||||||
const [featureNamingExample, setFeatureNamingExample] = useState(
|
const [featureNamingExample, setFeatureNamingExample] = useState(
|
||||||
initialFeatureNamingExample
|
initialFeatureNamingExample
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [featureNamingDescription, setFeatureNamingDescription] = useState(
|
||||||
|
initialFeatureNamingDescription
|
||||||
|
);
|
||||||
|
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
const { validateId } = useProjectApi();
|
const { validateId } = useProjectApi();
|
||||||
@ -63,6 +69,10 @@ const useProjectForm = (
|
|||||||
setFeatureNamingExample(initialFeatureNamingExample);
|
setFeatureNamingExample(initialFeatureNamingExample);
|
||||||
}, [initialFeatureNamingExample]);
|
}, [initialFeatureNamingExample]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFeatureNamingDescription(initialFeatureNamingDescription);
|
||||||
|
}, [initialFeatureNamingDescription]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setProjectStickiness(initialProjectStickiness);
|
setProjectStickiness(initialProjectStickiness);
|
||||||
}, [initialProjectStickiness]);
|
}, [initialProjectStickiness]);
|
||||||
@ -78,6 +88,7 @@ const useProjectForm = (
|
|||||||
featureNaming: {
|
featureNaming: {
|
||||||
pattern: featureNamingPattern,
|
pattern: featureNamingPattern,
|
||||||
example: featureNamingExample,
|
example: featureNamingExample,
|
||||||
|
description: featureNamingDescription,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -125,8 +136,10 @@ const useProjectForm = (
|
|||||||
featureLimit,
|
featureLimit,
|
||||||
featureNamingPattern,
|
featureNamingPattern,
|
||||||
featureNamingExample,
|
featureNamingExample,
|
||||||
|
featureNamingDescription,
|
||||||
setFeatureNamingPattern,
|
setFeatureNamingPattern,
|
||||||
setFeatureNamingExample,
|
setFeatureNamingExample,
|
||||||
|
setFeatureNamingDescription,
|
||||||
setProjectId,
|
setProjectId,
|
||||||
setProjectName,
|
setProjectName,
|
||||||
setProjectDesc,
|
setProjectDesc,
|
||||||
|
@ -16,6 +16,7 @@ export interface IProjectCard {
|
|||||||
export type FeatureNamingType = {
|
export type FeatureNamingType = {
|
||||||
pattern: string;
|
pattern: string;
|
||||||
example: string;
|
example: string;
|
||||||
|
description: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IProject {
|
export interface IProject {
|
||||||
|
@ -39,6 +39,7 @@ const SETTINGS_COLUMNS = [
|
|||||||
'feature_limit',
|
'feature_limit',
|
||||||
'feature_naming_pattern',
|
'feature_naming_pattern',
|
||||||
'feature_naming_example',
|
'feature_naming_example',
|
||||||
|
'feature_naming_description',
|
||||||
];
|
];
|
||||||
const SETTINGS_TABLE = 'project_settings';
|
const SETTINGS_TABLE = 'project_settings';
|
||||||
const PROJECT_ENVIRONMENTS = 'project_environments';
|
const PROJECT_ENVIRONMENTS = 'project_environments';
|
||||||
@ -240,6 +241,7 @@ class ProjectStore implements IProjectStore {
|
|||||||
feature_limit: project.featureLimit,
|
feature_limit: project.featureLimit,
|
||||||
feature_naming_pattern: project.featureNamingPattern,
|
feature_naming_pattern: project.featureNamingPattern,
|
||||||
feature_naming_example: project.featureNamingExample,
|
feature_naming_example: project.featureNamingExample,
|
||||||
|
feature_naming_description: project.featureNamingDescription,
|
||||||
})
|
})
|
||||||
.returning('*');
|
.returning('*');
|
||||||
return this.mapRow({ ...row[0], ...settingsRow[0] });
|
return this.mapRow({ ...row[0], ...settingsRow[0] });
|
||||||
@ -269,6 +271,8 @@ class ProjectStore implements IProjectStore {
|
|||||||
feature_limit: data.featureLimit,
|
feature_limit: data.featureLimit,
|
||||||
feature_naming_pattern: data.featureNaming?.pattern,
|
feature_naming_pattern: data.featureNaming?.pattern,
|
||||||
feature_naming_example: data.featureNaming?.example,
|
feature_naming_example: data.featureNaming?.example,
|
||||||
|
feature_naming_description:
|
||||||
|
data.featureNaming?.description,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.db(SETTINGS_TABLE).insert({
|
await this.db(SETTINGS_TABLE).insert({
|
||||||
@ -278,6 +282,7 @@ class ProjectStore implements IProjectStore {
|
|||||||
feature_limit: data.featureLimit,
|
feature_limit: data.featureLimit,
|
||||||
feature_naming_pattern: data.featureNaming?.pattern,
|
feature_naming_pattern: data.featureNaming?.pattern,
|
||||||
feature_naming_example: data.featureNaming?.example,
|
feature_naming_example: data.featureNaming?.example,
|
||||||
|
feature_naming_description: data.featureNaming?.description,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -573,6 +578,7 @@ class ProjectStore implements IProjectStore {
|
|||||||
featureNaming: {
|
featureNaming: {
|
||||||
pattern: row.feature_naming_pattern,
|
pattern: row.feature_naming_pattern,
|
||||||
example: row.feature_naming_example,
|
example: row.feature_naming_example,
|
||||||
|
description: row.feature_naming_description,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -11,14 +11,23 @@ export const createFeatureNamingPatternSchema = {
|
|||||||
nullable: true,
|
nullable: true,
|
||||||
description:
|
description:
|
||||||
'A JavaScript regular expression pattern, without the start and end delimiters. Optional flags are not allowed.',
|
'A JavaScript regular expression pattern, without the start and end delimiters. Optional flags are not allowed.',
|
||||||
example: '[a-z]{2,5}.team-[a-z]+.[a-z-]+',
|
example: '^[A-Za-z]+\\.[A-Za-z]+\\.[A-Za-z0-9-]+$',
|
||||||
},
|
},
|
||||||
example: {
|
example: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description:
|
description:
|
||||||
'An example of a feature name that matches the pattern. Must itself match the pattern supplied.',
|
'An example of a feature name that matches the pattern. Must itself match the pattern supplied.',
|
||||||
example: 'new-project.team-red.feature-1',
|
example: 'dx.feature1.1-135',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: 'string',
|
||||||
|
nullable: true,
|
||||||
|
description:
|
||||||
|
'A description of the pattern in a human-readable format. Will be shown to users when they create a new feature flag.',
|
||||||
|
example: `<project>.<featureName>.<ticket>
|
||||||
|
|
||||||
|
The flag name should contain the project name, the feature name, and the ticket number, each separated by a dot.`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {},
|
components: {},
|
||||||
|
@ -13,6 +13,7 @@ export const projectSchema = joi
|
|||||||
featureNaming: joi.object().keys({
|
featureNaming: joi.object().keys({
|
||||||
pattern: joi.string().allow(null).allow('').optional(),
|
pattern: joi.string().allow(null).allow('').optional(),
|
||||||
example: joi.string().allow(null).allow('').optional(),
|
example: joi.string().allow(null).allow('').optional(),
|
||||||
|
description: joi.string().allow(null).allow('').optional(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.options({ allowUnknown: false, stripUnknown: true });
|
.options({ allowUnknown: false, stripUnknown: true });
|
||||||
|
@ -192,6 +192,7 @@ export type ProjectMode = 'open' | 'protected';
|
|||||||
export interface IFeatureNaming {
|
export interface IFeatureNaming {
|
||||||
pattern: string | null;
|
pattern: string | null;
|
||||||
example: string | null;
|
example: string | null;
|
||||||
|
description: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProjectOverview {
|
export interface IProjectOverview {
|
||||||
|
@ -21,6 +21,7 @@ export interface IProjectInsert {
|
|||||||
featureLimit?: number;
|
featureLimit?: number;
|
||||||
featureNamingPattern?: string;
|
featureNamingPattern?: string;
|
||||||
featureNamingExample?: string;
|
featureNamingExample?: string;
|
||||||
|
featureNamingDescription?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProjectSettings {
|
export interface IProjectSettings {
|
||||||
@ -29,6 +30,7 @@ export interface IProjectSettings {
|
|||||||
featureLimit?: number;
|
featureLimit?: number;
|
||||||
featureNamingPattern?: string;
|
featureNamingPattern?: string;
|
||||||
featureNamingExample?: string;
|
featureNamingExample?: string;
|
||||||
|
featureNamingDescription?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProjectSettingsRow {
|
export interface IProjectSettingsRow {
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
exports.up = function (db, cb) {
|
||||||
|
db.runSql(
|
||||||
|
`
|
||||||
|
ALTER TABLE project_settings
|
||||||
|
ADD COLUMN IF NOT EXISTS "feature_naming_description" text;
|
||||||
|
`,
|
||||||
|
cb(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (db, cb) {
|
||||||
|
db.runSql(
|
||||||
|
`
|
||||||
|
ALTER TABLE project_settings DROP COLUMN IF EXISTS "feature_naming_description";
|
||||||
|
`,
|
||||||
|
cb,
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user