From d3708297cf2176c2b54d335379f20f92b6fbef19 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Thu, 20 Jul 2023 13:00:47 +0200 Subject: [PATCH] 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 --- .../FeatureTypeForm/FeatureTypeForm.tsx | 173 ++++++++++++++++++ .../featureTypes/FeatureTypesList.tsx | 33 +++- frontend/src/component/menu/Header/Header.tsx | 16 +- .../__snapshots__/routes.test.tsx.snap | 2 +- frontend/src/component/menu/routes.ts | 2 +- 5 files changed, 215 insertions(+), 11 deletions(-) create mode 100644 frontend/src/component/featureTypes/FeatureTypeForm/FeatureTypeForm.tsx diff --git a/frontend/src/component/featureTypes/FeatureTypeForm/FeatureTypeForm.tsx b/frontend/src/component/featureTypes/FeatureTypeForm/FeatureTypeForm.tsx new file mode 100644 index 0000000000..c10a707c5c --- /dev/null +++ b/frontend/src/component/featureTypes/FeatureTypeForm/FeatureTypeForm.tsx @@ -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 = ({ + featureTypes, + loading, +}) => { + const { featureTypeId } = useParams(); + const navigate = useNavigate(); + const featureType = featureTypes.find( + featureType => featureType.id === featureTypeId + ); + const [lifetime, setLifetime] = useState( + featureType?.lifetimeDays || 0 + ); + const [doesntExpire, setDoesntExpire] = useState( + !featureType?.lifetimeDays + ); + + if (!loading && !featureType) { + return ; + } + + const onChangeLifetime = (e: React.ChangeEvent) => { + const value = parseInt(trim(e.target.value), 10); + setLifetime(value); + if (value === 0) { + setDoesntExpire(true); + } + }; + + const onChangeDoesntExpire = (e: React.ChangeEvent) => { + 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 ( + 'FIXME: formatApiCode'} + > + + ({ + margin: theme.spacing(3, 0, 1), + display: 'flex', + alignItems: 'center', + })} + > + + Expected lifetime + + +

+ If your toggle exceeded lifetime of it's + type it will be marked as potencially stale. +

+
+ + Read more in the documentation + + + } + /> +
+ ({ + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + marginBottom: theme.spacing(1), + marginRight: 'auto', + })} + htmlFor="feature-toggle-expire" + > + + doesn't expire + + + + + Save feature toggle type + + + +
+
+ ); +}; diff --git a/frontend/src/component/featureTypes/FeatureTypesList.tsx b/frontend/src/component/featureTypes/FeatureTypesList.tsx index 29ef1c954c..c6b7c5c812 100644 --- a/frontend/src/component/featureTypes/FeatureTypesList.tsx +++ b/frontend/src/component/featureTypes/FeatureTypesList.tsx @@ -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 = () => { {}} + onClick={() => + navigate( + `/feature-toggle-type/edit/${featureType.id}` + ) + } permission={ADMIN} tooltipProps={{ - title: 'Edit feature toggle type', + title: `Edit ${featureType.name} feature toggle type`, }} > @@ -107,7 +117,7 @@ export const FeatureTypesList = () => { disableSortBy: true, }, ], - [] + [navigate] ); const data = useMemo( @@ -185,6 +195,23 @@ export const FeatureTypesList = () => { })} + + navigate(basePath)} + open + > + + + } + /> + ); }; diff --git a/frontend/src/component/menu/Header/Header.tsx b/frontend/src/component/menu/Header/Header.tsx index 077287e956..ae0eb48a47 100644 --- a/frontend/src/component/menu/Header/Header.tsx +++ b/frontend/src/component/menu/Header/Header.tsx @@ -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) { diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap index 7a384324e5..5d2f1a29e6 100644 --- a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap +++ b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap @@ -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", }, diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index f882be40b6..fd100c8382 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -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',