1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: rebrand feature toggle list as search (#5675)

This commit is contained in:
Jaanus Sellin 2023-12-19 13:42:14 +02:00 committed by GitHub
parent 5603e8683d
commit 8e09f08a05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 195 additions and 41 deletions

View File

@ -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(<FeatureToggleListActions onExportClick={() => {}} />);
const batchReviveButton = await screen.findByTitle('Group actions');
await userEvent.click(batchReviveButton!);
await screen.findByText('New feature toggle');
await screen.findByText('Export');
});

View File

@ -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<IFeatureToggleListActions> = ({
onExportClick,
}: IFeatureToggleListActions) => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const featuresExportImport = useUiFlag('featuresExportImport');
const createFeature = useCreateFeaturePath({
query: '',
project: 'default',
});
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const id = `feature-toggle-list-actions`;
const menuId = `${id}-menu`;
return (
<StyledActions
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<Tooltip title='Group actions' arrow describeChild>
<IconButton
id={id}
aria-controls={open ? menuId : undefined}
aria-haspopup='true'
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
type='button'
>
<MoreVert />
</IconButton>
</Tooltip>
<StyledPopover
id={menuId}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
transformOrigin={{
horizontal: 'right',
vertical: 'top',
}}
anchorOrigin={{
horizontal: 'right',
vertical: 'bottom',
}}
disableScrollLock={true}
>
<MenuList aria-labelledby={id}>
<PermissionHOC permission={CREATE_FEATURE}>
{({ hasAccess }) => (
<MenuItem
onClick={handleClose}
component={Link}
disabled={!hasAccess}
to={createFeature!.path}
>
<ListItemIcon>
<Add />
</ListItemIcon>
<ListItemText>
<Typography variant='body2'>
New feature toggle
</Typography>
</ListItemText>
</MenuItem>
)}
</PermissionHOC>
<ConditionallyRender
condition={featuresExportImport}
show={
<MenuItem
onClick={() => {
onExportClick();
handleClose();
}}
>
<ListItemIcon>
<FileDownload />
</ListItemIcon>
<ListItemText>
<Typography variant='body2'>
Export
</Typography>
</ListItemText>
</MenuItem>
}
/>
</MenuList>
</StyledPopover>
</StyledActions>
);
};

View File

@ -80,7 +80,7 @@ const setupApi = (features: APIFeature[], projects: APIProject[]) => {
};
const verifyTableFeature = async (feature: Partial<UIFeature>) => {
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)),

View File

@ -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={
<PageHeader
title='Feature toggles'
title='Search'
actions={
<>
<ConditionallyRender
@ -296,32 +288,8 @@ const FeatureToggleListTableComponent: VFC = () => {
>
View archive
</Link>
<ConditionallyRender
condition={Boolean(
uiConfig?.flags?.featuresExportImport,
)}
show={
<Tooltip
title='Export current selection'
arrow
>
<IconButton
onClick={() =>
setShowExportDialog(true)
}
sx={(theme) => ({
marginRight: theme.spacing(2),
})}
>
<FileDownload />
</IconButton>
</Tooltip>
}
/>
<CreateFeatureButton
loading={false}
filter={{ query: '', project: 'default' }}
<FeatureToggleListActions
onExportClick={() => setShowExportDialog(true)}
/>
</>
}

View File

@ -123,6 +123,7 @@ const Header: VFC = () => {
const [configRef, setConfigRef] = useState<HTMLButtonElement | null>(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 = () => {
<StyledNav>
<StyledLinks>
<StyledLink to='/projects'>Projects</StyledLink>
<StyledLink to={'/features'}>
Feature toggles
</StyledLink>
<ConditionallyRender
condition={hasSearch}
show={
<StyledLink to={'/search'}>Search</StyledLink>
}
elseShow={
<StyledLink to={'/features'}>
Feature toggles
</StyledLink>
}
/>
<StyledLink to='/playground'>Playground</StyledLink>
<StyledAdvancedNavButton
onClick={(e) => setConfigRef(e.currentTarget)}

View File

@ -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),

View File

@ -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