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

Fix: improve create new feature v2 (#441)

This commit is contained in:
Youssef Khedher 2021-10-20 12:12:48 +01:00 committed by GitHub
parent 9a383967dc
commit 2bce93a51b
9 changed files with 108 additions and 66 deletions

View File

@ -1,10 +1,10 @@
import { useEffect, useState } from 'react';
import { useEffect, useState, FormEvent } from 'react';
import { useHistory, useParams } from 'react-router';
import { useStyles } from './FeatureCreate.styles';
import { IFeatureViewParams } from '../../../interfaces/params';
import PageContent from '../../common/PageContent';
import useFeatureApi from '../../../hooks/api/actions/useFeatureApi/useFeatureApi';
import { CardActions, TextField } from '@material-ui/core';
import { CardActions } from '@material-ui/core';
import FeatureTypeSelect from '../FeatureView2/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect';
import {
CF_CREATE_BTN_ID,
@ -12,26 +12,28 @@ import {
CF_NAME_ID,
CF_TYPE_ID,
} from '../../../testIds';
import { loadNameFromUrl, trim } from '../../common/util';
import { getTogglePath } from '../../../utils/route-path-helpers';
import { IFeatureToggleDTO } from '../../../interfaces/featureToggle';
import { FormEventHandler } from 'react-router/node_modules/@types/react';
import { useCommonStyles } from '../../../common.styles';
import { FormButtons } from '../../common';
interface Errors {
name?: string;
description?: string;
}
import useQueryParams from '../../../hooks/useQueryParams';
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
import Input from '../../common/Input/Input';
import ProjectSelect from '../project-select-container';
import { projectFilterGenerator } from '../../../utils/project-filter-generator';
import useUser from '../../../hooks/api/getters/useUser/useUser';
import { trim } from '../../common/util';
import { CREATE_FEATURE } from '../../providers/AccessProvider/permissions';
const FeatureCreate = () => {
const styles = useStyles();
const commonStyles = useCommonStyles();
const { projectId } = useParams<IFeatureViewParams>();
const params = useQueryParams();
const { createFeatureToggle, validateFeatureToggleName } = useFeatureApi();
const history = useHistory();
const [toggle, setToggle] = useState<IFeatureToggleDTO>({
name: loadNameFromUrl(),
name: params.get('name') || '',
description: '',
type: 'release',
stale: false,
@ -39,7 +41,10 @@ const FeatureCreate = () => {
project: projectId,
archived: false,
});
const [errors, setErrors] = useState<Errors>({});
const [nameError, setNameError] = useState('');
const { uiConfig } = useUiConfig();
const { permissions } = useUser();
useEffect(() => {
window.onbeforeunload = () =>
@ -54,34 +59,40 @@ const FeatureCreate = () => {
const onCancel = () => history.push(`/projects/${projectId}`);
const validateName = async (featureToggleName: string) => {
const e = { ...errors };
if (featureToggleName.length > 0) {
try {
await validateFeatureToggleName(featureToggleName);
e.name = undefined;
} catch (err: any) {
e.name = err && err.message ? err.message : 'Could not check name';
setNameError(
err && err.message ? err.message : 'Could not check name'
);
}
}
setErrors(e);
};
const onSubmit = async (evt: FormEventHandler) => {
const onSubmit = async (evt: FormEvent<HTMLFormElement>) => {
evt.preventDefault();
const errorList = Object.values(errors).filter(i => i);
await validateName(toggle.name);
if (errorList.length > 0) {
if(!toggle.name) {
setNameError('Name is not allowed to be empty');
return;
}
if (nameError) {
return;
}
try {
await createFeatureToggle(projectId, toggle).then(() =>
history.push(getTogglePath(toggle.project, toggle.name, true))
);
await createFeatureToggle(toggle.project, toggle)
history.push(getTogglePath(toggle.project, toggle.name, uiConfig.flags.E));
// Trigger
} catch (e: any) {
if (e.toString().includes('not allowed to be empty')) {
setErrors({ name: 'Name is not allowed to be empty' });
} catch (err) {
if(err instanceof Error) {
if (err.toString().includes('not allowed to be empty')) {
setNameError('Name is not allowed to be empty');
}
}
}
};
@ -98,11 +109,8 @@ const FeatureCreate = () => {
<form onSubmit={onSubmit}>
<input type="hidden" name="project" value={projectId} />
<div className={styles.formContainer}>
<TextField
size="small"
variant="outlined"
<Input
label="Name"
required
placeholder="Unique-name"
className={styles.nameInput}
name="name"
@ -110,13 +118,16 @@ const FeatureCreate = () => {
'data-test': CF_NAME_ID,
}}
value={toggle.name}
error={errors.name !== undefined}
helperText={errors.name}
error={Boolean(nameError)}
helperText={nameError}
onBlur={v => validateName(v.target.value)}
onChange={v => setValue('name', trim(v.target.value))}
onChange={v => {
setValue('name', trim(v.target.value));
setNameError('');
}}
/>
</div>
<div className={styles.formContainer}>
<section className={styles.formContainer}>
<FeatureTypeSelect
value={toggle.type}
onChange={v => setValue('type', v.target.value)}
@ -127,18 +138,21 @@ const FeatureCreate = () => {
'data-test': CF_TYPE_ID,
}}
/>
</div>
</section>
<section className={styles.formContainer}>
<TextField
size="small"
variant="outlined"
<ProjectSelect
value={toggle.project}
onChange={v => setValue('project', v.target.value)}
filter={projectFilterGenerator({ permissions }, CREATE_FEATURE)}
/>
</section>
<section className={styles.formContainer}>
<Input
className={commonStyles.fullWidth}
multiline
rows={4}
label="Description"
placeholder="A short description of the feature toggle"
error={errors.description !== undefined}
helperText={errors.description}
value={toggle.description}
inputProps={{
'data-test': CF_DESC_ID,

View File

@ -54,7 +54,7 @@ const FeatureToggleList = ({
updateSetting('sort', typeof v === 'string' ? v.trim() : '');
};
const createURL = getCreateTogglePath(currentProjectId);
const createURL = getCreateTogglePath(currentProjectId, flags.E);
const renderFeatures = () => {
features.forEach(e => {

View File

@ -144,7 +144,7 @@ exports[`renders correctly with one feature 1`] = `
aria-disabled={true}
className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary Mui-disabled Mui-disabled"
data-test="NAVIGATE_TO_CREATE_FEATURE"
href="/projects/default/create-toggle?project=default"
href="/projects/default/create-toggle"
onBlur={[Function]}
onClick={[Function]}
onDragLeave={[Function]}
@ -344,7 +344,7 @@ exports[`renders correctly with one feature without permissions 1`] = `
aria-disabled={true}
className="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary Mui-disabled Mui-disabled"
data-test="NAVIGATE_TO_CREATE_FEATURE"
href="/projects/default/create-toggle?project=default"
href="/projects/default/create-toggle"
onBlur={[Function]}
onClick={[Function]}
onDragLeave={[Function]}

View File

@ -21,6 +21,7 @@ import FeatureSettings from './FeatureSettings/FeatureSettings';
import useLoading from '../../../hooks/useLoading';
import ConditionallyRender from '../../common/ConditionallyRender';
import { getCreateTogglePath } from '../../../utils/route-path-helpers';
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
const FeatureView2 = () => {
const { projectId, featureId } = useParams<IFeatureViewParams>();
@ -33,6 +34,7 @@ const FeatureView2 = () => {
const styles = useStyles();
const history = useHistory();
const ref = useLoading(loading);
const { uiConfig } = useUiConfig();
const basePath = `/projects/${projectId}/features2/${featureId}`;
@ -108,7 +110,15 @@ const FeatureView2 = () => {
<p>
The feature <strong>{featureId.substring(0, 30)}</strong>{' '}
does not exist. Do you want to &nbsp;
<Link to={getCreateTogglePath(projectId)}>create it</Link>
<Link
to={getCreateTogglePath(
projectId,
uiConfig.flags.E,
{name: featureId}
)}
>
create it
</Link>
&nbsp;?
</p>
</div>

View File

@ -21,6 +21,7 @@ import { Alert } from '@material-ui/lab';
import { getTogglePath } from '../../../../utils/route-path-helpers';
import useFeatureApi from '../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
import useFeature from '../../../../hooks/api/getters/useFeature/useFeature';
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
const CopyFeature = props => {
// static displayName = `AddFeatureComponent-${getDisplayName(Component)}`;
@ -32,6 +33,7 @@ const CopyFeature = props => {
const inputRef = useRef();
const { name: copyToggleName, id: projectId } = useParams();
const { feature } = useFeature(projectId, copyToggleName);
const { uiConfig } = useUiConfig();
useEffect(() => {
inputRef.current?.focus();
@ -70,7 +72,7 @@ const CopyFeature = props => {
{ name: newToggleName, replaceGroupId }
);
props.history.push(
getTogglePath(projectId, newToggleName)
getTogglePath(projectId, newToggleName, uiConfig.flags.E)
)
} catch (e) {
setApiError(e);
@ -96,7 +98,7 @@ const CopyFeature = props => {
You are about to create a new feature toggle by cloning the
configuration of feature toggle&nbsp;
<Link
to={getTogglePath(projectId, copyToggleName)}
to={getTogglePath(projectId, copyToggleName, uiConfig.flags.E)}
>
{copyToggleName}
</Link>
@ -115,6 +117,7 @@ const CopyFeature = props => {
variant="outlined"
size="small"
inputRef={inputRef}
required
/>
<FormControlLabel
control={

View File

@ -89,9 +89,7 @@ const CreateFeature = ({
<ProjectSelect
value={input.project}
defaultValue={project}
onChange={v => {
setValue('project', v.target.value);
}}
onChange={v => setValue('project', v.target.value)}
filter={projectFilterGenerator(user, CREATE_FEATURE)}
/>
</section>

View File

@ -52,6 +52,7 @@ class WrapperComponent extends Component {
};
validateName = async featureToggleName => {
if (featureToggleName.length > 0) {
const { errors } = { ...this.state };
try {
await validateName(featureToggleName);
@ -61,6 +62,7 @@ class WrapperComponent extends Component {
}
this.setState({ errors });
}
};
onSubmit = async evt => {

View File

@ -59,10 +59,7 @@ const ProjectFeatureToggles = ({
<ResponsiveButton
onClick={() =>
history.push(
getCreateTogglePath(
id,
uiConfig.flags.E
)
getCreateTogglePath(id, uiConfig.flags.E)
)
}
maxWidth="700px"

View File

@ -9,8 +9,26 @@ export const getToggleCopyPath = (
return `/projects/${projectId}/features/${featureToggleName}/strategies/copy`;
};
export const getCreateTogglePath = (projectId: string, newpath: boolean = false) => {
return newpath ? `/projects/${projectId}/create-toggle2` : `/projects/${projectId}/create-toggle?project=${projectId}`;
export const getCreateTogglePath = (
projectId: string,
newPath: boolean = false,
query?: Object
) => {
const path = newPath
? `/projects/${projectId}/create-toggle2`
: `/projects/${projectId}/create-toggle`;
let queryString;
if (query) {
queryString = Object.keys(query).reduce((acc, curr) => {
acc += `${curr}=${query[curr]}`;
return acc;
}, '');
}
if (queryString) {
return `${path}?${queryString}`;
}
return path;
};
export const getProjectEditPath = (projectId: string) => {