diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleActions.test.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleActions.test.tsx new file mode 100644 index 0000000000..1d311b9709 --- /dev/null +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleActions.test.tsx @@ -0,0 +1,23 @@ +import { screen } from '@testing-library/react'; +import { render } from 'utils/testRenderer'; +import { FeatureToggleListActions } from './FeatureToggleListActions'; +import userEvent from '@testing-library/user-event'; +import { testServerRoute, testServerSetup } from 'utils/testServer'; + +const server = testServerSetup(); +test('all options are drawn', async () => { + testServerRoute(server, '/api/admin/ui-config', { + flags: { + featuresExportImport: true, + }, + }); + + render( {}} />); + + const batchReviveButton = await screen.findByTitle('Group actions'); + + await userEvent.click(batchReviveButton!); + + await screen.findByText('New feature toggle'); + await screen.findByText('Export'); +}); diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.tsx new file mode 100644 index 0000000000..12bdea9926 --- /dev/null +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.tsx @@ -0,0 +1,134 @@ +import { FC, useState } from 'react'; +import { + IconButton, + ListItemIcon, + ListItemText, + MenuItem, + MenuList, + Popover, + styled, + Tooltip, + Typography, +} from '@mui/material'; +import { Add, FileDownload, MoreVert } from '@mui/icons-material'; +import { Link } from 'react-router-dom'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { useUiFlag } from 'hooks/useUiFlag'; +import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions'; +import { PermissionHOC } from 'component/common/PermissionHOC/PermissionHOC'; +import { useCreateFeaturePath } from 'component/feature/CreateFeatureButton/useCreateFeaturePath'; + +const StyledActions = styled('div')(({ theme }) => ({ + display: 'flex', + justifyContent: 'center', +})); + +const StyledPopover = styled(Popover)(({ theme }) => ({ + borderRadius: theme.shape.borderRadiusLarge, + padding: theme.spacing(1, 1.5), +})); + +interface IFeatureToggleListActions { + onExportClick: () => void; +} + +export const FeatureToggleListActions: FC = ({ + onExportClick, +}: IFeatureToggleListActions) => { + const [anchorEl, setAnchorEl] = useState(null); + const featuresExportImport = useUiFlag('featuresExportImport'); + const createFeature = useCreateFeaturePath({ + query: '', + project: 'default', + }); + + const open = Boolean(anchorEl); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + + const id = `feature-toggle-list-actions`; + const menuId = `${id}-menu`; + + return ( + { + e.preventDefault(); + e.stopPropagation(); + }} + > + + + + + + + + + {({ hasAccess }) => ( + + + + + + + New feature toggle + + + + )} + + { + onExportClick(); + handleClose(); + }} + > + + + + + + Export + + + + } + /> + + + + ); +}; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.test.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.test.tsx index ed2d1c3844..4527e929ba 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.test.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.test.tsx @@ -80,7 +80,7 @@ const setupApi = (features: APIFeature[], projects: APIProject[]) => { }; const verifyTableFeature = async (feature: Partial) => { - await screen.findByText('Feature toggles'); + await screen.findByText('Search'); await screen.findByText('Add Filter'); await Promise.all( Object.values(feature).map((value) => screen.findByText(value)), diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx index 896932fd92..366acaffa9 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx @@ -1,12 +1,5 @@ import { useCallback, useEffect, useMemo, useState, VFC } from 'react'; -import { - Box, - IconButton, - Link, - Tooltip, - useMediaQuery, - useTheme, -} from '@mui/material'; +import { Box, Link, useMediaQuery, useTheme } from '@mui/material'; import { Link as RouterLink } from 'react-router-dom'; import { createColumnHelper, useReactTable } from '@tanstack/react-table'; import { PaginatedTable, TablePlaceholder } from 'component/common/Table'; @@ -18,13 +11,11 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit import { PageContent } from 'component/common/PageContent/PageContent'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { FeatureSchema, FeatureSearchResponseSchema } from 'openapi'; -import { CreateFeatureButton } from '../CreateFeatureButton/CreateFeatureButton'; import { FeatureStaleCell } from './FeatureStaleCell/FeatureStaleCell'; import { Search } from 'component/common/Search/Search'; import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi'; import { FavoriteIconCell } from 'component/common/Table/cells/FavoriteIconCell/FavoriteIconCell'; import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader'; -import FileDownload from '@mui/icons-material/FileDownload'; import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; import { ExportDialog } from './ExportDialog'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; @@ -53,6 +44,7 @@ import { FeatureTagCell } from 'component/common/Table/cells/FeatureTagCell/Feat import { FeatureSegmentCell } from 'component/common/Table/cells/FeatureSegmentCell/FeatureSegmentCell'; import { useUiFlag } from 'hooks/useUiFlag'; import { FeatureToggleListTable as LegacyFeatureToggleListTable } from './LegacyFeatureToggleListTable'; +import { FeatureToggleListActions } from './FeatureToggleListActions/FeatureToggleListActions'; export const featuresPlaceholder = Array(15).fill({ name: 'Name of the feature', @@ -268,7 +260,7 @@ const FeatureToggleListTableComponent: VFC = () => { bodyClass='no-padding' header={ { > View archive - - - setShowExportDialog(true) - } - sx={(theme) => ({ - marginRight: theme.spacing(2), - })} - > - - - - } - /> - - setShowExportDialog(true)} /> } diff --git a/frontend/src/component/menu/Header/Header.tsx b/frontend/src/component/menu/Header/Header.tsx index dd584c5df4..dafc05a9f5 100644 --- a/frontend/src/component/menu/Header/Header.tsx +++ b/frontend/src/component/menu/Header/Header.tsx @@ -123,6 +123,7 @@ const Header: VFC = () => { const [configRef, setConfigRef] = useState(null); const disableNotifications = useUiFlag('disableNotifications'); + const hasSearch = useUiFlag('featureSearchFrontend'); const { uiConfig, isOss } = useUiConfig(); const smallScreen = useMediaQuery(theme.breakpoints.down('md')); const [openDrawer, setOpenDrawer] = useState(false); @@ -191,9 +192,17 @@ const Header: VFC = () => { Projects - - Feature toggles - + Search + } + elseShow={ + + Feature toggles + + } + /> Playground setConfigRef(e.currentTarget)} 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 47e03ef5e1..4449186498 100644 --- a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap +++ b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap @@ -119,10 +119,21 @@ exports[`returns all baseRoutes 1`] = ` "menu": { "mobile": true, }, + "notFlag": "featureSearchFrontend", "path": "/features", "title": "Feature toggles", "type": "protected", }, + { + "component": [Function], + "flag": "featureSearchFrontend", + "menu": { + "mobile": true, + }, + "path": "/search", + "title": "Search", + "type": "protected", + }, { "component": { "$$typeof": Symbol(react.lazy), diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index 0f930b829d..900c652049 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -147,6 +147,15 @@ export const routes: IRoute[] = [ component: FeatureToggleListTable, type: 'protected', menu: { mobile: true }, + notFlag: 'featureSearchFrontend', + }, + { + path: '/search', + title: 'Search', + component: FeatureToggleListTable, + type: 'protected', + menu: { mobile: true }, + flag: 'featureSearchFrontend', }, // Playground