1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-04 01:18:20 +02:00

Feat/new strategies table (#1012)

* fix: add flex to toolbarcontainer

* feat: add initial new table

* feat: add styled badge

* feat: remove dead code

* fix: remove useContext import

* fix: update context buttons to icon buttons

* feat: add loading

* fix: remove unused imports

* Update src/component/strategies/StrategiesList/PredefinedBadge/PredefinedBadge.tsx

Co-authored-by: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com>

* fix: update spacing to use theme

* fix: update loading

* fix: update type

Co-authored-by: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com>
This commit is contained in:
Fredrik Strand Oseberg 2022-05-24 10:58:06 +02:00 committed by GitHub
parent 9463c8df90
commit 7ba9d2a577
17 changed files with 310 additions and 447 deletions

View File

@ -72,6 +72,7 @@ const calculateTrialDaysRemaining = (
: undefined; : undefined;
}; };
// TODO - Cleanup to use theme instead of colors
const StyledBar = styled('aside')(({ theme }) => ({ const StyledBar = styled('aside')(({ theme }) => ({
position: 'relative', position: 'relative',
zIndex: 1, zIndex: 1,
@ -92,6 +93,7 @@ const StyledButton = styled(Button)(({ theme }) => ({
marginLeft: theme.spacing(2), marginLeft: theme.spacing(2),
})); }));
// TODO - Cleanup to use theme instead of colors
const StyledInfoIcon = styled(Info)(({ theme }) => ({ const StyledInfoIcon = styled(Info)(({ theme }) => ({
color: colors.blue[500], color: colors.blue[500],
})); }));

View File

@ -19,6 +19,7 @@ interface IPermissionIconButtonProps {
type?: 'button'; type?: 'button';
edge?: IconButtonProps['edge']; edge?: IconButtonProps['edge'];
tooltipProps?: Omit<ITooltipResolverProps, 'children'>; tooltipProps?: Omit<ITooltipResolverProps, 'children'>;
size?: string;
} }
interface IButtonProps extends IPermissionIconButtonProps { interface IButtonProps extends IPermissionIconButtonProps {

View File

@ -28,7 +28,6 @@ export const useStyles = makeStyles()(theme => ({
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
display: '-webkit-box', display: '-webkit-box',
WebkitBoxOrient: 'vertical',
}, },
description: { description: {
color: theme.palette.text.secondary, color: theme.palette.text.secondary,

View File

@ -13,7 +13,12 @@ interface ILinkCellProps {
subtitle?: string; subtitle?: string;
} }
export const LinkCell: FC<ILinkCellProps> = ({ title, to, subtitle }) => { export const LinkCell: FC<ILinkCellProps> = ({
title,
to,
subtitle,
children,
}) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const search = useSearchHighlightContext(); const search = useSearchHighlightContext();
@ -28,6 +33,7 @@ export const LinkCell: FC<ILinkCellProps> = ({ title, to, subtitle }) => {
}} }}
> >
<Highlighter search={search}>{title}</Highlighter> <Highlighter search={search}>{title}</Highlighter>
{children}
</span> </span>
<ConditionallyRender <ConditionallyRender
condition={Boolean(subtitle)} condition={Boolean(subtitle)}

View File

@ -5,40 +5,37 @@ import { Add } from '@mui/icons-material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
import { CREATE_CONTEXT_FIELD } from 'component/providers/AccessProvider/permissions'; import { CREATE_CONTEXT_FIELD } from 'component/providers/AccessProvider/permissions';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
interface IAddContextButtonProps {} interface IAddContextButtonProps {}
export const AddContextButton: VFC<IAddContextButtonProps> = () => { export const AddContextButton: VFC<IAddContextButtonProps> = () => {
const smallScreen = useMediaQuery('(max-width:700px)'); const smallScreen = useMediaQuery('(max-width:700px)');
const { hasAccess } = useContext(AccessContext);
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<ConditionallyRender <ConditionallyRender
condition={hasAccess(CREATE_CONTEXT_FIELD)} condition={smallScreen}
show={ show={
<ConditionallyRender <PermissionIconButton
condition={smallScreen} permission={CREATE_CONTEXT_FIELD}
show={ onClick={() => navigate('/context/create')}
<Tooltip title="Add context type" arrow> size="large"
<IconButton tooltipProps={{ title: 'Add context type' }}
onClick={() => navigate('/context/create')} >
size="large" <Add />
> </PermissionIconButton>
<Add /> }
</IconButton> elseShow={
</Tooltip> <PermissionButton
} onClick={() => navigate('/context/create')}
elseShow={ permission={CREATE_CONTEXT_FIELD}
<Button color="primary"
onClick={() => navigate('/context/create')} variant="contained"
color="primary" >
variant="contained" New context field
> </PermissionButton>
New context field
</Button>
}
/>
} }
/> />
); );

View File

@ -21,6 +21,7 @@ export const ContextActionsCell: VFC<IContextActionsCellProps> = ({
return ( return (
<Box <Box
data-loading
sx={{ sx={{
display: 'flex', display: 'flex',
px: 2, px: 2,

View File

@ -1,4 +1,4 @@
import { useContext, useMemo, useState, VFC } from 'react'; import { useMemo, useState, VFC } from 'react';
import { useGlobalFilter, useSortBy, useTable } from 'react-table'; import { useGlobalFilter, useSortBy, useTable } from 'react-table';
import { import {
Table, Table,
@ -12,9 +12,7 @@ import {
import { PageContent } from 'component/common/PageContent/PageContent'; import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { UPDATE_CONTEXT_FIELD } from 'component/providers/AccessProvider/permissions';
import { Dialogue as ConfirmDialogue } from 'component/common/Dialogue/Dialogue'; import { Dialogue as ConfirmDialogue } from 'component/common/Dialogue/Dialogue';
import AccessContext from 'contexts/AccessContext';
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext'; import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
import useContextsApi from 'hooks/api/actions/useContextsApi/useContextsApi'; import useContextsApi from 'hooks/api/actions/useContextsApi/useContextsApi';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
@ -28,12 +26,12 @@ import { Adjust } from '@mui/icons-material';
import { Box } from '@mui/material'; import { Box } from '@mui/material';
const ContextList: VFC = () => { const ContextList: VFC = () => {
const { hasAccess } = useContext(AccessContext);
const [showDelDialogue, setShowDelDialogue] = useState(false); const [showDelDialogue, setShowDelDialogue] = useState(false);
const [name, setName] = useState<string>(); const [name, setName] = useState<string>();
const { context, refetchUnleashContext, loading } = useUnleashContext(); const { context, refetchUnleashContext, loading } = useUnleashContext();
const { removeContext } = useContextsApi(); const { removeContext } = useContextsApi();
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
const data = useMemo(() => { const data = useMemo(() => {
if (loading) { if (loading) {
return Array(5).fill({ return Array(5).fill({
@ -57,6 +55,7 @@ const ContextList: VFC = () => {
id: 'Icon', id: 'Icon',
Cell: () => ( Cell: () => (
<Box <Box
data-loading
sx={{ sx={{
pl: 2, pl: 2,
pr: 1, pr: 1,
@ -79,12 +78,8 @@ const ContextList: VFC = () => {
}: any) => ( }: any) => (
<LinkCell <LinkCell
title={name} title={name}
to={
hasAccess(UPDATE_CONTEXT_FIELD)
? `/context/edit/${name}`
: undefined
}
subtitle={description} subtitle={description}
data-loading
/> />
), ),
sortType: 'alphanumeric', sortType: 'alphanumeric',
@ -118,7 +113,7 @@ const ContextList: VFC = () => {
sortType: 'number', sortType: 'number',
}, },
], ],
[hasAccess] []
); );
const initialState = useMemo( const initialState = useMemo(
@ -172,6 +167,7 @@ const ContextList: VFC = () => {
return ( return (
<PageContent <PageContent
isLoading={loading}
header={ header={
<PageHeader <PageHeader
title="Context fields" title="Context fields"

View File

@ -17,6 +17,7 @@ export const useStyles = makeStyles()(theme => ({
}, },
toolbarContainer: { toolbarContainer: {
flexShrink: 0, flexShrink: 0,
display: 'flex',
}, },
innerContainer: { innerContainer: {
padding: '1rem 2rem', padding: '1rem 2rem',

View File

@ -1,6 +1,7 @@
import { FeatureToggleListContainer } from 'component/feature/FeatureToggleList/FeatureToggleListContainer'; import { FeatureToggleListContainer } from 'component/feature/FeatureToggleList/FeatureToggleListContainer';
import { StrategyView } from 'component/strategies/StrategyView/StrategyView'; import { StrategyView } from 'component/strategies/StrategyView/StrategyView';
import { StrategiesList } from 'component/strategies/StrategiesList/StrategiesList'; import { StrategiesList } from 'component/strategies/StrategiesList/StrategiesList';
import { ArchiveListContainer } from 'component/archive/ArchiveListContainer'; import { ArchiveListContainer } from 'component/archive/ArchiveListContainer';
import { TagTypeList } from 'component/tags/TagTypeList/TagTypeList'; import { TagTypeList } from 'component/tags/TagTypeList/TagTypeList';
import { AddonList } from 'component/addons/AddonList/AddonList'; import { AddonList } from 'component/addons/AddonList/AddonList';

View File

@ -0,0 +1,40 @@
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { useMediaQuery } from '@mui/material';
import { CREATE_STRATEGY } from 'component/providers/AccessProvider/permissions';
import { ADD_NEW_STRATEGY_ID } from 'utils/testIds';
import { Add } from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
export const AddStrategyButton = () => {
const smallScreen = useMediaQuery('(max-width:700px)');
const navigate = useNavigate();
return (
<ConditionallyRender
condition={smallScreen}
show={
<PermissionIconButton
data-testid={ADD_NEW_STRATEGY_ID}
onClick={() => navigate('/strategies/create')}
permission={CREATE_STRATEGY}
size="large"
tooltipProps={{ title: 'New strategy' }}
>
<Add />
</PermissionIconButton>
}
elseShow={
<PermissionButton
onClick={() => navigate('/strategies/create')}
color="primary"
permission={CREATE_STRATEGY}
data-testid={ADD_NEW_STRATEGY_ID}
>
New strategy
</PermissionButton>
}
/>
);
};

View File

@ -0,0 +1,15 @@
import { styled } from '@mui/material';
export const PredefinedBadge = () => {
return <StyledBadge>Predefined</StyledBadge>;
};
const StyledBadge = styled('div')(({ theme }) => ({
padding: theme.spacing(0.3, 1.25),
backgroundColor: theme.palette.predefinedBadgeColor,
textDecoration: 'none',
color: theme.palette.text.primary,
display: 'inline-block',
borderRadius: theme.shape.borderRadius,
marginLeft: theme.spacing(1.5),
}));

View File

@ -1,14 +0,0 @@
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
listItem: {
padding: '0',
['& a']: {
textDecoration: 'none',
color: 'inherit',
},
'&:hover': {
backgroundColor: theme.palette.grey[200],
},
},
}));

View File

@ -1,28 +0,0 @@
import { MemoryRouter } from 'react-router-dom';
import { StrategiesList } from './StrategiesList';
import renderer from 'react-test-renderer';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import UIProvider from 'component/providers/UIProvider/UIProvider';
import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider';
import { ThemeProvider } from 'themes/ThemeProvider';
import { AccessProviderMock } from 'component/providers/AccessProvider/AccessProviderMock';
test('renders correctly', () => {
const tree = renderer.create(
<MemoryRouter>
<ThemeProvider>
<AnnouncerProvider>
<UIProvider>
<AccessProviderMock
permissions={[{ permission: ADMIN }]}
>
<StrategiesList />
</AccessProviderMock>
</UIProvider>
</AnnouncerProvider>
</ThemeProvider>
</MemoryRouter>
);
expect(tree).toMatchSnapshot();
});

View File

@ -1,16 +1,7 @@
import { useContext, useState } from 'react'; import { useState, useMemo } from 'react';
import { Link, useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import useMediaQuery from '@mui/material/useMediaQuery'; import { IconButton, Tooltip, Box } from '@mui/material';
import { import {
IconButton,
List,
ListItem,
ListItemAvatar,
ListItemText,
Tooltip,
} from '@mui/material';
import {
Add,
Delete, Delete,
Edit, Edit,
Extension, Extension,
@ -18,26 +9,35 @@ import {
VisibilityOff, VisibilityOff,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { import {
CREATE_STRATEGY,
DELETE_STRATEGY, DELETE_STRATEGY,
UPDATE_STRATEGY, UPDATE_STRATEGY,
} from 'component/providers/AccessProvider/permissions'; } from 'component/providers/AccessProvider/permissions';
import {
Table,
SortableTableHeader,
TableBody,
TableCell,
TableRow,
TablePlaceholder,
TableSearch,
} from 'component/common/Table';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PageContent } from 'component/common/PageContent/PageContent'; import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { useStyles } from './StrategiesList.styles';
import AccessContext from 'contexts/AccessContext';
import { Dialogue } from 'component/common/Dialogue/Dialogue'; import { Dialogue } from 'component/common/Dialogue/Dialogue';
import { ADD_NEW_STRATEGY_ID } from 'utils/testIds';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { formatStrategyName } from 'utils/strategyNames'; import { formatStrategyName } from 'utils/strategyNames';
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies'; import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
import useStrategiesApi from 'hooks/api/actions/useStrategiesApi/useStrategiesApi'; import useStrategiesApi from 'hooks/api/actions/useStrategiesApi/useStrategiesApi';
import useToast from 'hooks/useToast'; import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError'; import { formatUnknownError } from 'utils/formatUnknownError';
import { IStrategy } from 'interfaces/strategy'; import { IStrategy } from 'interfaces/strategy';
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { sortTypes } from 'utils/sortTypes';
import { useTable, useGlobalFilter, useSortBy } from 'react-table';
import { AddStrategyButton } from './AddStrategyButton/AddStrategyButton';
import { PredefinedBadge } from './PredefinedBadge/PredefinedBadge';
interface IDialogueMetaData { interface IDialogueMetaData {
show: boolean; show: boolean;
title: string; title: string;
@ -46,9 +46,6 @@ interface IDialogueMetaData {
export const StrategiesList = () => { export const StrategiesList = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { classes: styles } = useStyles();
const smallScreen = useMediaQuery('(max-width:700px)');
const { hasAccess } = useContext(AccessContext);
const [dialogueMetaData, setDialogueMetaData] = useState<IDialogueMetaData>( const [dialogueMetaData, setDialogueMetaData] = useState<IDialogueMetaData>(
{ {
show: false, show: false,
@ -56,50 +53,137 @@ export const StrategiesList = () => {
onConfirm: () => {}, onConfirm: () => {},
} }
); );
const { strategies, refetchStrategies } = useStrategies();
const { strategies, refetchStrategies, loading } = useStrategies();
const { removeStrategy, deprecateStrategy, reactivateStrategy } = const { removeStrategy, deprecateStrategy, reactivateStrategy } =
useStrategiesApi(); useStrategiesApi();
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
const headerButton = () => ( const data = useMemo(() => {
<ConditionallyRender if (loading) {
condition={hasAccess(CREATE_STRATEGY)} return Array(5).fill({
show={ name: 'Context name',
<ConditionallyRender description: 'Context description when loading',
condition={smallScreen} });
show={ }
<PermissionIconButton
data-testid={ADD_NEW_STRATEGY_ID} return strategies.map(
onClick={() => navigate('/strategies/create')} ({ name, description, editable, deprecated }) => ({
permission={CREATE_STRATEGY} name,
tooltipProps={{ title: 'New strategy' }} description,
editable,
deprecated,
})
);
}, [strategies, loading]);
const columns = useMemo(
() => [
{
id: 'Icon',
Cell: () => (
<Box
data-loading
sx={{
pl: 2,
pr: 1,
display: 'flex',
alignItems: 'center',
}}
>
<Extension color="disabled" />
</Box>
),
},
{
Header: 'Name',
accessor: 'name',
width: '90%',
Cell: ({
row: {
original: { name, description, deprecated, editable },
},
}: any) => {
const subTitleText = deprecated
? `${description} (deprecated)`
: description;
return (
<LinkCell
data-loading
title={formatStrategyName(name)}
subtitle={subTitleText}
to={`/strategies/${name}`}
> >
<Add /> <ConditionallyRender
</PermissionIconButton> condition={!editable}
} show={() => <PredefinedBadge />}
elseShow={ />
<PermissionButton </LinkCell>
onClick={() => navigate('/strategies/create')} );
color="primary" },
permission={CREATE_STRATEGY} sortType: 'alphanumeric',
data-testid={ADD_NEW_STRATEGY_ID} },
> {
New strategy Header: 'Actions',
</PermissionButton> id: 'Actions',
} align: 'center',
/> Cell: ({ row: { original } }: any) => (
} <Box
/> sx={{ display: 'flex', justifyContent: 'flex-end' }}
data-loading
>
<ConditionallyRender
condition={original.deprecated}
show={reactivateButton(original)}
elseShow={deprecateButton(original)}
/>
{editButton(original)}
{deleteButton(original)}
</Box>
),
width: 150,
disableSortBy: true,
},
{
accessor: 'description',
disableSortBy: true,
},
{
accessor: 'sortOrder',
sortType: 'number',
},
],
[]
); );
const strategyLink = (name: string, deprecated: boolean) => ( const initialState = useMemo(
<Link to={`/strategies/${name}`}> () => ({
<strong>{formatStrategyName(name)}</strong> sortBy: [{ id: 'name', desc: false }],
<ConditionallyRender hiddenColumns: ['description', 'sortOrder'],
condition={deprecated} }),
show={<small> (Deprecated)</small>} []
/> );
</Link>
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
state: { globalFilter },
setGlobalFilter,
} = useTable(
{
columns: columns as any[], // TODO: fix after `react-table` v8 update
data,
initialState,
sortTypes,
autoResetGlobalFilter: false,
autoResetSortBy: false,
disableSortRemove: true,
},
useGlobalFilter,
useSortBy
); );
const onReactivateStrategy = (strategy: IStrategy) => { const onReactivateStrategy = (strategy: IStrategy) => {
@ -176,10 +260,7 @@ export const StrategiesList = () => {
<ConditionallyRender <ConditionallyRender
condition={strategy.name === 'default'} condition={strategy.name === 'default'}
show={ show={
<Tooltip <Tooltip title="You cannot deprecate the default strategy">
title="You cannot deprecate the default strategy"
arrow
>
<div> <div>
<IconButton disabled size="large"> <IconButton disabled size="large">
<Visibility titleAccess="Deprecate strategy" /> <Visibility titleAccess="Deprecate strategy" />
@ -216,7 +297,7 @@ export const StrategiesList = () => {
</PermissionIconButton> </PermissionIconButton>
} }
elseShow={ elseShow={
<Tooltip title="You cannot delete a built-in strategy" arrow> <Tooltip title="You cannot edit a built-in strategy" arrow>
<div> <div>
<IconButton disabled size="large"> <IconButton disabled size="large">
<Edit titleAccess="Edit strategy" /> <Edit titleAccess="Edit strategy" />
@ -251,56 +332,85 @@ export const StrategiesList = () => {
/> />
); );
const strategyList = () =>
strategies.map(strategy => (
<ListItem key={strategy.name} className={styles.listItem}>
<ListItemAvatar>
<Extension color="disabled" />
</ListItemAvatar>
<ListItemText
primary={strategyLink(strategy?.name, strategy?.deprecated)}
secondary={strategy.description}
/>
<ConditionallyRender
condition={strategy.deprecated}
show={reactivateButton(strategy)}
elseShow={deprecateButton(strategy)}
/>
<ConditionallyRender
condition={hasAccess(UPDATE_STRATEGY)}
show={editButton(strategy)}
/>
<ConditionallyRender
condition={hasAccess(DELETE_STRATEGY)}
show={deleteButton(strategy)}
/>
</ListItem>
));
const onDialogConfirm = () => { const onDialogConfirm = () => {
dialogueMetaData?.onConfirm(); dialogueMetaData?.onConfirm();
setDialogueMetaData(prev => ({ ...prev, show: false })); setDialogueMetaData((prev: IDialogueMetaData) => ({
...prev,
show: false,
}));
}; };
return ( return (
<PageContent <PageContent
header={<PageHeader title="Strategies" actions={headerButton()} />} isLoading={loading}
header={
<PageHeader
title="Strategies"
actions={
<>
<TableSearch
initialValue={globalFilter}
onChange={setGlobalFilter}
/>
<PageHeader.Divider />
<AddStrategyButton />
</>
}
/>
}
> >
<SearchHighlightProvider value={globalFilter}>
<Table {...getTableProps()}>
<SortableTableHeader headerGroups={headerGroups} />
<TableBody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row);
return (
<TableRow hover {...row.getRowProps()}>
{row.cells.map(cell => (
<TableCell {...cell.getCellProps()}>
{cell.render('Cell')}
</TableCell>
))}
</TableRow>
);
})}
</TableBody>
</Table>
</SearchHighlightProvider>
<ConditionallyRender
condition={rows.length === 0}
show={
<ConditionallyRender
condition={globalFilter?.length > 0}
show={
<TablePlaceholder>
No strategies found matching &ldquo;
{globalFilter}
&rdquo;
</TablePlaceholder>
}
elseShow={
<TablePlaceholder>
No strategies available. Get started by adding
one.
</TablePlaceholder>
}
/>
}
/>
<Dialogue <Dialogue
open={dialogueMetaData.show} open={dialogueMetaData.show}
onClick={onDialogConfirm} onClick={onDialogConfirm}
title={dialogueMetaData?.title} title={dialogueMetaData?.title}
onClose={() => onClose={() =>
setDialogueMetaData(prev => ({ ...prev, show: false })) setDialogueMetaData((prev: IDialogueMetaData) => ({
...prev,
show: false,
}))
} }
/> />
<List>
<ConditionallyRender
condition={strategies.length > 0}
show={<>{strategyList()}</>}
elseShow={<ListItem>No strategies found</ListItem>}
/>
</List>
</PageContent> </PageContent>
); );
}; };

View File

@ -1,270 +0,0 @@
// Vitest Snapshot v1
exports[`renders correctly 1`] = `
[
<div>
<div
className="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation1 tss-15wj2kz-container mui-177gdp-MuiPaper-root"
>
<div
className="tss-1ywhhai-headerContainer"
>
<div
className="tss-1ylehva-headerContainer"
>
<div
className="tss-1uxyh7x-topContainer"
>
<div
className="tss-sd6bs4-header"
data-loading={true}
>
<h1
className="MuiTypography-root MuiTypography-h1 tss-7lbvh4-headerTitle mui-ylrecv-MuiTypography-root"
>
Strategies
</h1>
</div>
<div
className="tss-u5t8ea-headerActions"
>
<span
id="useId-0"
>
<button
aria-describedby="useId-0"
className="MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonBase-root mui-1aw3qf3-MuiButtonBase-root-MuiButton-root"
data-testid="ADD_NEW_STRATEGY_ID"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
onContextMenu={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={0}
type="button"
>
New strategy
<span
className="MuiButton-endIcon MuiButton-iconSizeMedium mui-9tj150-MuiButton-endIcon"
/>
<span
className="MuiTouchRipple-root mui-8je8zh-MuiTouchRipple-root"
/>
</button>
</span>
</div>
</div>
</div>
</div>
<div
className="tss-54jt3w-bodyContainer"
>
<ul
className="MuiList-root MuiList-padding mui-h4y409-MuiList-root"
>
<li
className="MuiListItem-root MuiListItem-gutters MuiListItem-padding tss-ynyzms-listItem mui-vlytkl-MuiListItem-root"
disabled={false}
>
<div
className="MuiListItemAvatar-root mui-1e9lk82-MuiListItemAvatar-root"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root MuiSvgIcon-colorDisabled MuiSvgIcon-fontSizeMedium mui-1db085k-MuiSvgIcon-root"
data-testid="ExtensionIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z"
/>
</svg>
</div>
<div
className="MuiListItemText-root MuiListItemText-multiline mui-konndc-MuiListItemText-root"
>
<span
className="MuiTypography-root MuiTypography-body1 MuiListItemText-primary mui-1k7ov6f-MuiTypography-root"
>
<a
href="/strategies/flexibleRollout"
onClick={[Function]}
>
<strong>
Gradual rollout
</strong>
</a>
</span>
<p
className="MuiTypography-root MuiTypography-body2 MuiListItemText-secondary mui-of6c4k-MuiTypography-root"
>
Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit.
</p>
</div>
<div>
<div
aria-label="Deprecate strategy"
aria-labelledby={null}
className=""
data-mui-internal-clone-element={true}
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
<button
className="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeLarge mui-mf1cb5-MuiButtonBase-root-MuiIconButton-root"
disabled={false}
onBlur={[Function]}
onClick={[Function]}
onContextMenu={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={0}
type="button"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium mui-i4bv87-MuiSvgIcon-root"
data-testid="VisibilityIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"
/>
</svg>
<span
className="MuiTouchRipple-root mui-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
<div
aria-label="You cannot delete a built-in strategy"
aria-labelledby={null}
className=""
data-mui-internal-clone-element={true}
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
<button
className="MuiButtonBase-root Mui-disabled MuiIconButton-root Mui-disabled MuiIconButton-sizeLarge mui-mf1cb5-MuiButtonBase-root-MuiIconButton-root"
disabled={true}
onBlur={[Function]}
onContextMenu={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={-1}
type="button"
>
<svg
className="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium mui-i4bv87-MuiSvgIcon-root"
data-testid="EditIcon"
focusable="false"
role="img"
viewBox="0 0 24 24"
>
<path
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 0 0-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
/>
<title>
Edit strategy
</title>
</svg>
</button>
</div>
<div
aria-label="You cannot delete a built-in strategy"
aria-labelledby={null}
className=""
data-mui-internal-clone-element={true}
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
<button
className="MuiButtonBase-root Mui-disabled MuiIconButton-root Mui-disabled MuiIconButton-sizeLarge mui-mf1cb5-MuiButtonBase-root-MuiIconButton-root"
disabled={true}
onBlur={[Function]}
onContextMenu={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
tabIndex={-1}
type="button"
>
<svg
className="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium mui-i4bv87-MuiSvgIcon-root"
data-testid="DeleteIcon"
focusable="false"
role="img"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
<title>
Delete strategy
</title>
</svg>
</button>
</div>
</li>
</ul>
</div>
</div>
</div>,
<div
aria-atomic={true}
aria-live="polite"
className="tss-i8rqz1-container"
data-testid="ANNOUNCER_ELEMENT_TEST_ID"
role="status"
>
Navigated to Strategies
</div>,
]
`;

View File

@ -81,6 +81,7 @@ export default createTheme({
highlight: '#FFEACC', highlight: '#FFEACC',
sidebarContainer: 'rgba(32,32,33, 0.2)', sidebarContainer: 'rgba(32,32,33, 0.2)',
grey: colors.grey, grey: colors.grey,
predefinedBadgeColor: colors.green[100],
text: { text: {
primary: colors.grey[900], primary: colors.grey[900],
secondary: colors.grey[800], secondary: colors.grey[800],

View File

@ -49,6 +49,11 @@ declare module '@mui/material/styles' {
abandoned: string; abandoned: string;
}; };
dividerAlternative: string; dividerAlternative: string;
/**
* Color for predefined badge
*/
predefinedBadgeColor: string;
/** /**
* For table header hover effect. * For table header hover effect.
*/ */