mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +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:
parent
77a365e667
commit
d3708297cf
@ -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>
|
||||||
|
);
|
||||||
|
};
|
@ -1,4 +1,5 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { Route, Routes, useNavigate } from 'react-router-dom';
|
||||||
import { useSortBy, useTable } from 'react-table';
|
import { useSortBy, useTable } from 'react-table';
|
||||||
import { sortTypes } from 'utils/sortTypes';
|
import { sortTypes } from 'utils/sortTypes';
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
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 { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
import { Edit } from '@mui/icons-material';
|
import { Edit } from '@mui/icons-material';
|
||||||
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
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 = () => {
|
export const FeatureTypesList = () => {
|
||||||
const { featureTypes, loading } = useFeatureTypes();
|
const { featureTypes, loading } = useFeatureTypes();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@ -93,10 +99,14 @@ export const FeatureTypesList = () => {
|
|||||||
<PermissionIconButton
|
<PermissionIconButton
|
||||||
disabled={!featureType.id}
|
disabled={!featureType.id}
|
||||||
data-loading="true"
|
data-loading="true"
|
||||||
onClick={() => {}}
|
onClick={() =>
|
||||||
|
navigate(
|
||||||
|
`/feature-toggle-type/edit/${featureType.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
permission={ADMIN}
|
permission={ADMIN}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
title: 'Edit feature toggle type',
|
title: `Edit ${featureType.name} feature toggle type`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Edit />
|
<Edit />
|
||||||
@ -107,7 +117,7 @@ export const FeatureTypesList = () => {
|
|||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[]
|
[navigate]
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = useMemo(
|
const data = useMemo(
|
||||||
@ -185,6 +195,23 @@ export const FeatureTypesList = () => {
|
|||||||
})}
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</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>
|
</PageContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -107,6 +107,11 @@ const StyledIconButton = styled(IconButton)(({ theme }) => ({
|
|||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const mapRouteLink = (route: INavigationMenuItem) => ({
|
||||||
|
...route,
|
||||||
|
path: route.path.replace('/*', ''),
|
||||||
|
});
|
||||||
|
|
||||||
const Header: VFC = () => {
|
const Header: VFC = () => {
|
||||||
const { onSetThemeMode, themeMode } = useThemeMode();
|
const { onSetThemeMode, themeMode } = useThemeMode();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -143,7 +148,8 @@ const Header: VFC = () => {
|
|||||||
menu: {},
|
menu: {},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.filter(filterByConfig(uiConfig)),
|
.filter(filterByConfig(uiConfig))
|
||||||
|
.map(mapRouteLink),
|
||||||
mobileRoutes: getCondensedRoutes(routes.mobileRoutes)
|
mobileRoutes: getCondensedRoutes(routes.mobileRoutes)
|
||||||
.concat([
|
.concat([
|
||||||
{
|
{
|
||||||
@ -152,14 +158,12 @@ const Header: VFC = () => {
|
|||||||
menu: {},
|
menu: {},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.filter(filterByConfig(uiConfig)),
|
.filter(filterByConfig(uiConfig))
|
||||||
|
.map(mapRouteLink),
|
||||||
adminRoutes: adminMenuRoutes
|
adminRoutes: adminMenuRoutes
|
||||||
.filter(filterByConfig(uiConfig))
|
.filter(filterByConfig(uiConfig))
|
||||||
.filter(filterByMode)
|
.filter(filterByMode)
|
||||||
.map(route => ({
|
.map(mapRouteLink),
|
||||||
...route,
|
|
||||||
path: route.path.replace('/*', ''),
|
|
||||||
})),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (smallScreen) {
|
if (smallScreen) {
|
||||||
|
@ -200,7 +200,7 @@ exports[`returns all baseRoutes 1`] = `
|
|||||||
"advanced": true,
|
"advanced": true,
|
||||||
"mobile": true,
|
"mobile": true,
|
||||||
},
|
},
|
||||||
"path": "/feature-toggle-type",
|
"path": "/feature-toggle-type/*",
|
||||||
"title": "Feature toggle types",
|
"title": "Feature toggle types",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
|
@ -212,7 +212,7 @@ export const routes: IRoute[] = [
|
|||||||
|
|
||||||
// Feature types
|
// Feature types
|
||||||
{
|
{
|
||||||
path: '/feature-toggle-type',
|
path: '/feature-toggle-type/*',
|
||||||
title: 'Feature toggle types',
|
title: 'Feature toggle types',
|
||||||
component: FeatureTypesList,
|
component: FeatureTypesList,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
|
Loading…
Reference in New Issue
Block a user