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

feat: Feature toggle type - edit form (#4269)

## About the changes

![image](https://github.com/Unleash/unleash/assets/2625371/f09bb538-9bb1-4c6b-85d7-e7895486e794)


Task: https://linear.app/unleash/issue/1-1127/add-front-end


### Important files
frontend/src/component/featureTypes/FeatureTypeForm/FeatureTypeForm.tsx

## Discussion points

**`FIXME`** will be addressed when integrating with API
This commit is contained in:
Tymoteusz Czech 2023-07-20 13:00:47 +02:00 committed by GitHub
parent 77a365e667
commit d3708297cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 215 additions and 11 deletions

View File

@ -0,0 +1,173 @@
import { type FormEventHandler, type VFC, useState } from 'react';
import { Box, Button, Typography, Checkbox, styled } from '@mui/material';
import { useNavigate, useParams } from 'react-router-dom';
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import NotFound from 'component/common/NotFound/NotFound';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { GO_BACK } from 'constants/navigate';
import Input from 'component/common/Input/Input';
import { FeatureTypeSchema } from 'openapi';
import { trim } from 'component/common/util';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
type FeatureTypeFormProps = {
featureTypes: FeatureTypeSchema[];
loading: boolean;
};
const StyledButtons = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'flex-end',
marginTop: 'auto',
gap: theme.spacing(2),
paddingTop: theme.spacing(4),
}));
const StyledForm = styled(Box)(() => ({
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
}));
export const FeatureTypeForm: VFC<FeatureTypeFormProps> = ({
featureTypes,
loading,
}) => {
const { featureTypeId } = useParams();
const navigate = useNavigate();
const featureType = featureTypes.find(
featureType => featureType.id === featureTypeId
);
const [lifetime, setLifetime] = useState<number>(
featureType?.lifetimeDays || 0
);
const [doesntExpire, setDoesntExpire] = useState<boolean>(
!featureType?.lifetimeDays
);
if (!loading && !featureType) {
return <NotFound />;
}
const onChangeLifetime = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseInt(trim(e.target.value), 10);
setLifetime(value);
if (value === 0) {
setDoesntExpire(true);
}
};
const onChangeDoesntExpire = (e: React.ChangeEvent<HTMLInputElement>) => {
setDoesntExpire(e.target.checked);
if (lifetime === 0) {
setLifetime(featureType?.lifetimeDays || 1);
}
};
const isIncorrect =
!doesntExpire && (Number.isNaN(lifetime) || lifetime < 0);
const onSubmit: FormEventHandler = e => {
e.preventDefault();
if (isIncorrect) return;
const value = doesntExpire ? 0 : lifetime;
console.log('FIXME: onSubmit', value);
};
return (
<FormTemplate
modal
title={
loading
? 'Edit toggle type'
: `Edit toggle type: ${featureType?.name}`
}
description={featureType?.description || ''}
documentationLink="https://docs.getunleash.io/reference/feature-toggle-types"
documentationLinkLabel="Feature toggle types documentation"
formatApiCode={() => 'FIXME: formatApiCode'}
>
<StyledForm component="form" onSubmit={onSubmit}>
<Typography
sx={theme => ({
margin: theme.spacing(3, 0, 1),
display: 'flex',
alignItems: 'center',
})}
>
<Box component="label" htmlFor="feature-toggle-lifetime">
Expected lifetime
</Box>
<HelpIcon
htmlTooltip
tooltip={
<>
<p>
If your toggle exceeded lifetime of it's
type it will be marked as potencially stale.
</p>
<br />
<a
href="https://docs.getunleash.io/reference/feature-toggle-types#expected-lifetime"
target="_blank"
rel="noreferrer"
>
Read more in the documentation
</a>
</>
}
/>
</Typography>
<Box
component="label"
sx={theme => ({
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
marginBottom: theme.spacing(1),
marginRight: 'auto',
})}
htmlFor="feature-toggle-expire"
>
<Checkbox
checked={doesntExpire || lifetime === 0}
id="feature-toggle-expire"
onChange={onChangeDoesntExpire}
/>
<Box>doesn't expire</Box>
</Box>
<Input
autoFocus
disabled={doesntExpire}
type="number"
label="Lifetime in days"
id="feature-toggle-lifetime"
value={doesntExpire ? '0' : `${lifetime}`}
onChange={onChangeLifetime}
error={isIncorrect}
/>
<StyledButtons>
<PermissionButton
permission={ADMIN}
variant="contained"
color="primary"
type="submit"
disabled={loading || isIncorrect}
>
Save feature toggle type
</PermissionButton>
<Button
type="button"
color="primary"
onClick={() => navigate(GO_BACK)}
disabled={loading}
>
Cancel
</Button>
</StyledButtons>
</StyledForm>
</FormTemplate>
);
};

View File

@ -1,4 +1,5 @@
import { useMemo } from 'react';
import { Route, Routes, useNavigate } from 'react-router-dom';
import { useSortBy, useTable } from 'react-table';
import { sortTypes } from 'utils/sortTypes';
import { PageContent } from 'component/common/PageContent/PageContent';
@ -20,11 +21,16 @@ import PermissionIconButton from 'component/common/PermissionIconButton/Permissi
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { Edit } from '@mui/icons-material';
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
import { FeatureTypeForm } from './FeatureTypeForm/FeatureTypeForm';
const basePath = '/feature-toggle-type';
export const FeatureTypesList = () => {
const { featureTypes, loading } = useFeatureTypes();
const theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const navigate = useNavigate();
const columns = useMemo(
() => [
@ -93,10 +99,14 @@ export const FeatureTypesList = () => {
<PermissionIconButton
disabled={!featureType.id}
data-loading="true"
onClick={() => {}}
onClick={() =>
navigate(
`/feature-toggle-type/edit/${featureType.id}`
)
}
permission={ADMIN}
tooltipProps={{
title: 'Edit feature toggle type',
title: `Edit ${featureType.name} feature toggle type`,
}}
>
<Edit />
@ -107,7 +117,7 @@ export const FeatureTypesList = () => {
disableSortBy: true,
},
],
[]
[navigate]
);
const data = useMemo(
@ -185,6 +195,23 @@ export const FeatureTypesList = () => {
})}
</TableBody>
</Table>
<Routes>
<Route
path="edit/:featureTypeId"
element={
<SidebarModal
label="Edit feature toggle type"
onClose={() => navigate(basePath)}
open
>
<FeatureTypeForm
featureTypes={featureTypes}
loading={loading}
/>
</SidebarModal>
}
/>
</Routes>
</PageContent>
);
};

View File

@ -107,6 +107,11 @@ const StyledIconButton = styled(IconButton)(({ theme }) => ({
borderRadius: 100,
}));
const mapRouteLink = (route: INavigationMenuItem) => ({
...route,
path: route.path.replace('/*', ''),
});
const Header: VFC = () => {
const { onSetThemeMode, themeMode } = useThemeMode();
const theme = useTheme();
@ -143,7 +148,8 @@ const Header: VFC = () => {
menu: {},
},
])
.filter(filterByConfig(uiConfig)),
.filter(filterByConfig(uiConfig))
.map(mapRouteLink),
mobileRoutes: getCondensedRoutes(routes.mobileRoutes)
.concat([
{
@ -152,14 +158,12 @@ const Header: VFC = () => {
menu: {},
},
])
.filter(filterByConfig(uiConfig)),
.filter(filterByConfig(uiConfig))
.map(mapRouteLink),
adminRoutes: adminMenuRoutes
.filter(filterByConfig(uiConfig))
.filter(filterByMode)
.map(route => ({
...route,
path: route.path.replace('/*', ''),
})),
.map(mapRouteLink),
};
if (smallScreen) {

View File

@ -200,7 +200,7 @@ exports[`returns all baseRoutes 1`] = `
"advanced": true,
"mobile": true,
},
"path": "/feature-toggle-type",
"path": "/feature-toggle-type/*",
"title": "Feature toggle types",
"type": "protected",
},

View File

@ -212,7 +212,7 @@ export const routes: IRoute[] = [
// Feature types
{
path: '/feature-toggle-type',
path: '/feature-toggle-type/*',
title: 'Feature toggle types',
component: FeatureTypesList,
type: 'protected',