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:
parent
5603e8683d
commit
8e09f08a05
@ -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');
|
||||
});
|
@ -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>
|
||||
);
|
||||
};
|
@ -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)),
|
||||
|
@ -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)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
@ -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)}
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user