From ef495a35ee341c2eb6a50854b897b570496cdfe1 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Tue, 18 Jul 2023 13:46:06 +0200 Subject: [PATCH] Feature toggle types list (#4260) ## About the changes ![image](https://github.com/Unleash/unleash/assets/2625371/0f66333f-5ed9-4e44-b658-2e7c3606a2c3) --- .../admin/roles/RolesTable/RolesTable.tsx | 4 +- .../featureTypes/FeatureTypesList.tsx | 190 ++++++++++++++++++ .../__snapshots__/routes.test.tsx.snap | 11 + frontend/src/component/menu/routes.ts | 11 + frontend/src/interfaces/uiConfig.ts | 1 + 5 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 frontend/src/component/featureTypes/FeatureTypesList.tsx diff --git a/frontend/src/component/admin/roles/RolesTable/RolesTable.tsx b/frontend/src/component/admin/roles/RolesTable/RolesTable.tsx index 32f4ada533..b4824391c3 100644 --- a/frontend/src/component/admin/roles/RolesTable/RolesTable.tsx +++ b/frontend/src/component/admin/roles/RolesTable/RolesTable.tsx @@ -5,12 +5,11 @@ import { IRole, PredefinedRoleType } from 'interfaces/role'; import useToast from 'hooks/useToast'; import { formatUnknownError } from 'utils/formatUnknownError'; import { PageContent } from 'component/common/PageContent/PageContent'; -import { useMediaQuery } from '@mui/material'; +import { useTheme, useMediaQuery } from '@mui/material'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { useFlexLayout, useSortBy, useTable } from 'react-table'; import { sortTypes } from 'utils/sortTypes'; import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; -import theme from 'themes/theme'; import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns'; import { useSearch } from 'hooks/useSearch'; import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; @@ -42,6 +41,7 @@ export const RolesTable = ({ setSelectedRole, }: IRolesTableProps) => { const { setToastData, setToastApiError } = useToast(); + const theme = useTheme(); const { roles, projectRoles, refetch, loading } = useRoles(); const { removeRole } = useRolesApi(); diff --git a/frontend/src/component/featureTypes/FeatureTypesList.tsx b/frontend/src/component/featureTypes/FeatureTypesList.tsx new file mode 100644 index 0000000000..29ef1c954c --- /dev/null +++ b/frontend/src/component/featureTypes/FeatureTypesList.tsx @@ -0,0 +1,190 @@ +import { useMemo } from 'react'; +import { useSortBy, useTable } from 'react-table'; +import { sortTypes } from 'utils/sortTypes'; +import { PageContent } from 'component/common/PageContent/PageContent'; +import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes'; +import { PageHeader } from 'component/common/PageHeader/PageHeader'; +import { Box, Typography, useMediaQuery, useTheme } from '@mui/material'; +import { + Table, + TableBody, + TableCell, + TableRow, + SortableTableHeader, +} from 'component/common/Table'; +import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; +import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons'; +import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; +import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell'; +import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; +import { ADMIN } from 'component/providers/AccessProvider/permissions'; +import { Edit } from '@mui/icons-material'; +import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns'; + +export const FeatureTypesList = () => { + const { featureTypes, loading } = useFeatureTypes(); + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); + + const columns = useMemo( + () => [ + { + accessor: 'id', + Cell: ({ value }: { value: string }) => { + const IconComponent = getFeatureTypeIcons(value); + return ( + + } + /> + ); + }, + width: 50, + disableSortBy: true, + }, + { + Header: 'Name', + accessor: 'name', + minWidth: 125, + Cell: TextCell, + }, + { + Header: 'Description', + accessor: 'description', + width: '80%', + Cell: ({ value }: { value: string }) => ( + + {value} + + ), + disableSortBy: true, + }, + { + Header: 'Lifetime', + accessor: 'lifetimeDays', + Cell: ({ value }: { value: number }) => { + if (value) { + return ( + + {value === 1 ? '1 day' : `${value} days`} + + ); + } + + return doesn't expire; + }, + sortInverted: true, + minWidth: 150, + }, + { + Header: 'Actions', + Cell: ({ row: { original: featureType } }: any) => ( + ({ padding: theme.spacing(0.5, 0) })}> + + {}} + permission={ADMIN} + tooltipProps={{ + title: 'Edit feature toggle type', + }} + > + + + + + ), + disableSortBy: true, + }, + ], + [] + ); + + const data = useMemo( + () => + loading + ? Array(5).fill({ + id: '', + name: 'Loading...', + description: 'Loading...', + lifetimeDays: 1, + }) + : featureTypes, + [loading, featureTypes] + ); + + const { + getTableProps, + getTableBodyProps, + headerGroups, + rows, + prepareRow, + setHiddenColumns, + } = useTable( + { + columns: columns as any[], + data, + sortTypes, + autoResetSortBy: false, + disableSortRemove: true, + }, + useSortBy + ); + + useConditionallyHiddenColumns( + [ + { + condition: isSmallScreen, + columns: ['description'], + }, + ], + setHiddenColumns, + columns + ); + + return ( + + ({ + fontSize: theme.fontSizes.mainHeader, + })} + > + Feature toggle types + + + } + > + + + + {rows.map(row => { + prepareRow(row); + return ( + + {row.cells.map(cell => ( + + {cell.render('Cell')} + + ))} + + ); + })} + +
+
+ ); +}; 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 9dde07c560..7a384324e5 100644 --- a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap +++ b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap @@ -193,6 +193,17 @@ exports[`returns all baseRoutes 1`] = ` "title": "Context fields", "type": "protected", }, + { + "component": [Function], + "flag": "configurableFeatureTypeLifetimes", + "menu": { + "advanced": true, + "mobile": true, + }, + "path": "/feature-toggle-type", + "title": "Feature toggle types", + "type": "protected", + }, { "component": [Function], "menu": {}, diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index b782a76bb7..f882be40b6 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -44,6 +44,7 @@ import { LazyAdmin } from 'component/admin/LazyAdmin'; import { LazyProject } from 'component/project/Project/LazyProject'; import { AdminRedirect } from 'component/admin/AdminRedirect'; import { LoginHistory } from 'component/loginHistory/LoginHistory'; +import { FeatureTypesList } from 'component/featureTypes/FeatureTypesList'; export const routes: IRoute[] = [ // Splash @@ -209,6 +210,16 @@ export const routes: IRoute[] = [ menu: { mobile: true, advanced: true }, }, + // Feature types + { + path: '/feature-toggle-type', + title: 'Feature toggle types', + component: FeatureTypesList, + type: 'protected', + menu: { mobile: true, advanced: true }, + flag: 'configurableFeatureTypeLifetimes', + }, + // Strategies { path: '/strategies/create', diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index ac620831d4..eea3f36acc 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -53,6 +53,7 @@ export interface IFlags { customRootRoles?: boolean; strategyVariant?: boolean; newProjectLayout?: boolean; + configurableFeatureTypeLifetimes?: boolean; } export interface IVersionInfo {