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:
parent
9a383967dc
commit
2bce93a51b
@ -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 };
|
||||
try {
|
||||
await validateFeatureToggleName(featureToggleName);
|
||||
e.name = undefined;
|
||||
} catch (err: any) {
|
||||
e.name = err && err.message ? err.message : 'Could not check name';
|
||||
if (featureToggleName.length > 0) {
|
||||
try {
|
||||
await validateFeatureToggleName(featureToggleName);
|
||||
} catch (err: any) {
|
||||
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,
|
||||
|
@ -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 => {
|
||||
|
@ -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]}
|
||||
|
@ -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
|
||||
<Link to={getCreateTogglePath(projectId)}>create it</Link>
|
||||
<Link
|
||||
to={getCreateTogglePath(
|
||||
projectId,
|
||||
uiConfig.flags.E,
|
||||
{name: featureId}
|
||||
)}
|
||||
>
|
||||
create it
|
||||
</Link>
|
||||
?
|
||||
</p>
|
||||
</div>
|
||||
|
@ -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
|
||||
<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={
|
||||
|
@ -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>
|
||||
|
@ -52,15 +52,17 @@ class WrapperComponent extends Component {
|
||||
};
|
||||
|
||||
validateName = async featureToggleName => {
|
||||
const { errors } = { ...this.state };
|
||||
try {
|
||||
await validateName(featureToggleName);
|
||||
errors.name = undefined;
|
||||
} catch (err) {
|
||||
errors.name = err.message;
|
||||
}
|
||||
if (featureToggleName.length > 0) {
|
||||
const { errors } = { ...this.state };
|
||||
try {
|
||||
await validateName(featureToggleName);
|
||||
errors.name = undefined;
|
||||
} catch (err) {
|
||||
errors.name = err.message;
|
||||
}
|
||||
|
||||
this.setState({ errors });
|
||||
this.setState({ errors });
|
||||
}
|
||||
};
|
||||
|
||||
onSubmit = async evt => {
|
||||
|
@ -55,14 +55,11 @@ const ProjectFeatureToggles = ({
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
<ResponsiveButton
|
||||
onClick={() =>
|
||||
history.push(
|
||||
getCreateTogglePath(
|
||||
id,
|
||||
uiConfig.flags.E
|
||||
)
|
||||
getCreateTogglePath(id, uiConfig.flags.E)
|
||||
)
|
||||
}
|
||||
maxWidth="700px"
|
||||
|
@ -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) => {
|
||||
|
Loading…
Reference in New Issue
Block a user