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

fix: new create toggle page

This commit is contained in:
Ivar Conradi Østhus 2021-10-15 14:16:08 +02:00
parent 58ff86e3bd
commit edd6706ffe
8 changed files with 257 additions and 3 deletions

View File

@ -0,0 +1,17 @@
import { makeStyles } from '@material-ui/core/styles';
export const useStyles = makeStyles(theme => ({
bodyContainer: {
borderRadius: '12.5px',
backgroundColor: '#fff',
padding: '2rem',
},
formContainer: {
marginBottom: '1.5rem',
maxWidth: '350px',
},
nameInput: {
marginRight: `1.5rem`,
minWidth: `250px`
}
}));

View File

@ -0,0 +1,165 @@
import { useEffect, useState } 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 FeatureTypeSelect from '../FeatureView2/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect';
import {
CF_CREATE_BTN_ID,
CF_DESC_ID,
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;
}
const FeatureCreate = () => {
const styles = useStyles();
const commonStyles = useCommonStyles();
const { projectId } = useParams<IFeatureViewParams>();
const { createFeatureToggle, validateFeatureToggleName } = useFeatureApi();
const history = useHistory();
const [ toggle, setToggle ] = useState<IFeatureToggleDTO>({
name: loadNameFromUrl(),
description: '',
type: 'release',
stale: false,
variants: [],
project: projectId,
archived: false,
});
const [errors, setErrors] = useState<Errors>({});
useEffect(() => {
window.onbeforeunload = () =>
'Data will be lost if you leave the page, are you sure?';
return () => {
//@ts-ignore
window.onbeforeunload = false;
};
}, []);
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';
}
setErrors(e);
};
const onSubmit = async (evt: FormEventHandler) => {
evt.preventDefault();
const errorList = Object.values(errors).filter(i => i);
if (errorList.length > 0) {
return;
}
try {
await createFeatureToggle(projectId, toggle).then(() =>
history.push(
getTogglePath(toggle.project, toggle.name, true)
)
);
// Trigger
} catch (e: any) {
if (e.toString().includes('not allowed to be empty')) {
setErrors({ name: 'Name is not allowed to be empty' })
}
}
};
const setValue = (field:string, value:string) => {
setToggle({...toggle, [field]: value})
}
return (
<PageContent headerContent="Create feature toggle" bodyClass={styles.bodyContainer}>
<form onSubmit={onSubmit}>
<input type="hidden" name="project" value={projectId} />
<div className={styles.formContainer}>
<TextField
size="small"
variant="outlined"
label="Name"
required
placeholder="Unique-name"
className={styles.nameInput}
name="name"
inputProps={{
'data-test': CF_NAME_ID,
}}
value={toggle.name}
error={errors.name !== undefined}
helperText={errors.name}
onBlur={v => validateName(v.target.value)}
onChange={v => setValue('name', trim(v.target.value))}
/>
</div>
<div className={styles.formContainer}>
<FeatureTypeSelect
value={toggle.type}
onChange={v => setValue('type', v.target.value)}
label={'Toggle type'}
id="feature-type-select"
editable
inputProps={{
'data-test': CF_TYPE_ID,
}}
/>
</div>
<section className={styles.formContainer}>
<TextField
size="small"
variant="outlined"
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,
}}
onChange={v => setValue('description', v.target.value)}
/>
</section>
<CardActions>
<FormButtons
submitText={'Create'}
primaryButtonTestId={CF_CREATE_BTN_ID}
onCancel={onCancel}
/>
</CardActions>
</form>
</PageContent>
);
};
export default FeatureCreate;

View File

@ -57,6 +57,15 @@ Array [
"title": ":name",
"type": "protected",
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/projects/:id/features",
"path": "/projects/:projectId/create-toggle2",
"title": "Create feature toggle",
"type": "protected",
},
Object {
"component": [Function],
"layout": "main",

View File

@ -40,6 +40,7 @@ import RedirectArchive from '../feature/RedirectArchive/RedirectArchive';
import EnvironmentList from '../environments/EnvironmentList/EnvironmentList';
import CreateEnvironment from '../environments/CreateEnvironment/CreateEnvironment';
import FeatureView2 from '../feature/FeatureView2/FeatureView2';
import FeatureCreate from '../feature/FeatureCreate/FeatureCreate'
export const routes = [
// Project
@ -98,6 +99,15 @@ export const routes = [
layout: 'main',
menu: {},
},
{
path: '/projects/:projectId/create-toggle2',
parent: '/projects/:id/features',
title: 'Create feature toggle',
component: FeatureCreate,
type: 'protected',
layout: 'main',
menu: {},
},
{
path: '/projects/:id/create-toggle',
parent: '/projects',

View File

@ -15,6 +15,7 @@ import ResponsiveButton from '../../../common/ResponsiveButton/ResponsiveButton'
import FeatureToggleListNew from '../../../feature/FeatureToggleListNew/FeatureToggleListNew';
import { useStyles } from './ProjectFeatureToggles.styles';
import { CREATE_FEATURE } from '../../../AccessProvider/permissions';
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
interface IProjectFeatureToggles {
features: IFeatureToggleListItem[];
@ -29,6 +30,7 @@ const ProjectFeatureToggles = ({
const { id } = useParams();
const history = useHistory();
const { hasAccess } = useContext(AccessContext);
const { uiConfig } = useUiConfig();
return (
<PageContent
@ -59,7 +61,7 @@ const ProjectFeatureToggles = ({
<ResponsiveButton
onClick={() =>
history.push(
getCreateTogglePath(id)
getCreateTogglePath(id, uiConfig.flags.E)
)
}
maxWidth="700px"

View File

@ -1,3 +1,4 @@
import { IFeatureToggleDTO } from '../../../../interfaces/featureToggle';
import { ITag } from '../../../../interfaces/tags';
import useAPI from '../useApi/useApi';
@ -6,6 +7,44 @@ const useFeatureApi = () => {
propagateErrors: true,
});
const validateFeatureToggleName = async (
name: string,
) => {
const path = `api/admin/features/validate`;
const req = createRequest(path, {
method: 'POST',
body: JSON.stringify({ name }),
});
try {
const res = await makeRequest(req.caller, req.id);
return res;
} catch (e) {
throw e;
}
};
const createFeatureToggle = async (
projectId: string,
featureToggle: IFeatureToggleDTO,
) => {
const path = `api/admin/projects/${projectId}/features`;
const req = createRequest(path, {
method: 'POST',
body: JSON.stringify(featureToggle),
});
try {
const res = await makeRequest(req.caller, req.id);
return res;
} catch (e) {
throw e;
}
};
const toggleFeatureEnvironmentOn = async (
projectId: string,
featureId: string,
@ -164,6 +203,8 @@ const useFeatureApi = () => {
};
return {
validateFeatureToggleName,
createFeatureToggle,
changeFeatureProject,
errors,
toggleFeatureEnvironmentOn,

View File

@ -11,6 +11,16 @@ export interface IEnvironments {
enabled: boolean;
}
export interface IFeatureToggleDTO {
stale: boolean;
archived: boolean;
description: string;
name: string;
project: string;
type: string;
variants: IFeatureVariant[];
}
export interface IFeatureToggle {
stale: boolean;
archived: boolean;

View File

@ -9,8 +9,8 @@ export const getToggleCopyPath = (
return `/projects/${projectId}/features/${featureToggleName}/strategies/copy`;
};
export const getCreateTogglePath = (projectId: string) => {
return `/projects/${projectId}/create-toggle?project=${projectId}`;
export const getCreateTogglePath = (projectId: string, newpath: boolean = false) => {
return newpath ? `/projects/${projectId}/create-toggle2` : `/projects/${projectId}/create-toggle?project=${projectId}`;
};
export const getProjectEditPath = (projectId: string) => {