mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-09 13:47:13 +02:00
chore: split standard and advanced strategy types (#10433)
https://linear.app/unleash/issue/2-3733/update-strategy-types-to-match-the-new-designs This updates our strategy types page to match the new designs. Part of this means visually separating what we are considering "standard" strategies from "advanced" strategies. <img width="1520" height="981" alt="image" src="https://github.com/user-attachments/assets/2682013b-d9df-453d-9427-62871e74d46a" />
This commit is contained in:
parent
0f565c50e9
commit
c5b37fc7c2
@ -31,6 +31,7 @@ import { Badge } from 'component/common/Badge/Badge';
|
|||||||
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
||||||
import { CustomStrategyInfo } from '../CustomStrategyInfo/CustomStrategyInfo.tsx';
|
import { CustomStrategyInfo } from '../CustomStrategyInfo/CustomStrategyInfo.tsx';
|
||||||
import { AddStrategyButton } from './AddStrategyButton/AddStrategyButton.tsx';
|
import { AddStrategyButton } from './AddStrategyButton/AddStrategyButton.tsx';
|
||||||
|
import { usePageTitle } from 'hooks/usePageTitle.ts';
|
||||||
|
|
||||||
interface IDialogueMetaData {
|
interface IDialogueMetaData {
|
||||||
show: boolean;
|
show: boolean;
|
||||||
@ -38,89 +39,70 @@ interface IDialogueMetaData {
|
|||||||
onConfirm: () => void;
|
onConfirm: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledBox = styled(Box)(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: theme.spacing(2),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledBadge = styled(Badge)(({ theme }) => ({
|
const StyledBadge = styled(Badge)(({ theme }) => ({
|
||||||
marginLeft: theme.spacing(1),
|
marginLeft: theme.spacing(1),
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledTypography = styled(Typography)(({ theme }) => ({
|
const StyledTitle = styled(Box)(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: theme.spacing(1),
|
||||||
fontSize: theme.fontSizes.mainHeader,
|
fontSize: theme.fontSizes.mainHeader,
|
||||||
|
width: '100%',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const Subtitle: FC<{
|
const StyledSubtitle = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
'& > span': {
|
||||||
|
fontWeight: theme.fontWeight.bold,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Title: FC<{
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
link: string;
|
link: string;
|
||||||
}> = ({ title, description, link }) => (
|
}> = ({ title, description, link }) => (
|
||||||
<StyledTypography>
|
<StyledTitle>
|
||||||
{title}
|
{title}
|
||||||
<HelpIcon
|
<HelpIcon
|
||||||
htmlTooltip
|
htmlTooltip
|
||||||
tooltip={
|
tooltip={
|
||||||
<>
|
<>
|
||||||
<Typography
|
<Typography variant='body2' component='p' sx={{ mb: 1 }}>
|
||||||
variant='body2'
|
|
||||||
component='p'
|
|
||||||
sx={(theme) => ({ marginBottom: theme.spacing(1) })}
|
|
||||||
>
|
|
||||||
{description}
|
{description}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Link href={link} target='_blank' variant='body2'>
|
<Link
|
||||||
|
href={link}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
variant='body2'
|
||||||
|
>
|
||||||
Read more in the documentation
|
Read more in the documentation
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</StyledTypography>
|
</StyledTitle>
|
||||||
);
|
|
||||||
|
|
||||||
const CustomStrategyTitle: FC = () => (
|
|
||||||
<Box
|
|
||||||
sx={(theme) => ({
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: theme.spacing(1.5),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Subtitle
|
|
||||||
title='Custom strategies'
|
|
||||||
description='Custom activation strategies let you define your own activation strategies to use with Unleash.'
|
|
||||||
link='https://docs.getunleash.io/reference/custom-activation-strategies'
|
|
||||||
/>
|
|
||||||
<AddStrategyButton />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
const PredefinedStrategyTitle = () => (
|
|
||||||
<Box>
|
|
||||||
<Subtitle
|
|
||||||
title='Predefined strategies'
|
|
||||||
description='Activation strategies let you enable a feature only for a specified audience. Different strategies use different parameters. Predefined strategies are bundled with Unleash.'
|
|
||||||
link='https://docs.getunleash.io/reference/activation-strategies'
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const StrategyDeprecationWarning = () => (
|
const StrategyDeprecationWarning = () => (
|
||||||
<Alert severity='warning' sx={{ mb: 2 }}>
|
<Alert severity='warning' sx={{ mb: 2 }}>
|
||||||
Custom strategies are deprecated and may be removed in a future major
|
Custom strategies are deprecated and may be removed in a future major
|
||||||
version. We recommend not using custom strategies going forward and
|
version. We recommend not using custom strategies going forward and
|
||||||
instead using the predefined strategies with{' '}
|
instead using the gradual rollout strategy with{' '}
|
||||||
<Link
|
<Link
|
||||||
href={
|
href={
|
||||||
'https://docs.getunleash.io/reference/activation-strategies#constraints'
|
'https://docs.getunleash.io/reference/activation-strategies#constraints'
|
||||||
}
|
}
|
||||||
target='_blank'
|
target='_blank'
|
||||||
variant='body2'
|
rel='noopener noreferrer'
|
||||||
>
|
>
|
||||||
constraints
|
constraints
|
||||||
</Link>
|
</Link>
|
||||||
@ -129,6 +111,28 @@ const StrategyDeprecationWarning = () => (
|
|||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const RecommendationAlert = () => (
|
||||||
|
<Alert severity='info' sx={{ mb: 2 }}>
|
||||||
|
We recommend using gradual rollout. You can customize it with{' '}
|
||||||
|
<Link
|
||||||
|
href='https://docs.getunleash.io/reference/activation-strategies#constraints'
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
>
|
||||||
|
constraints
|
||||||
|
</Link>{' '}
|
||||||
|
and{' '}
|
||||||
|
<Link
|
||||||
|
href='https://docs.getunleash.io/reference/activation-strategies#variants'
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
>
|
||||||
|
variants
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
|
||||||
export const StrategiesList = () => {
|
export const StrategiesList = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [dialogueMetaData, setDialogueMetaData] = useState<IDialogueMetaData>(
|
const [dialogueMetaData, setDialogueMetaData] = useState<IDialogueMetaData>(
|
||||||
@ -144,6 +148,8 @@ export const StrategiesList = () => {
|
|||||||
useStrategiesApi();
|
useStrategiesApi();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
|
||||||
|
usePageTitle('Strategy types');
|
||||||
|
|
||||||
const data = useMemo(() => {
|
const data = useMemo(() => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
const mock = Array(5).fill({
|
const mock = Array(5).fill({
|
||||||
@ -152,22 +158,28 @@ export const StrategiesList = () => {
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
all: mock,
|
all: mock,
|
||||||
predefined: mock,
|
standard: mock,
|
||||||
|
advanced: mock,
|
||||||
custom: mock,
|
custom: mock,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const all = strategies.map(
|
const all = strategies.map(
|
||||||
({ name, description, editable, deprecated }) => ({
|
({ name, description, editable, deprecated, advanced }) => ({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
editable,
|
editable,
|
||||||
deprecated,
|
deprecated,
|
||||||
|
advanced,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const predefined = all.filter((strategy) => !strategy.editable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
all,
|
all,
|
||||||
predefined: all.filter((strategy) => !strategy.editable),
|
standard: predefined.filter((strategy) => !strategy.advanced),
|
||||||
|
advanced: predefined.filter((strategy) => strategy.advanced),
|
||||||
custom: all.filter((strategy) => strategy.editable),
|
custom: all.filter((strategy) => strategy.editable),
|
||||||
};
|
};
|
||||||
}, [strategies, loading]);
|
}, [strategies, loading]);
|
||||||
@ -350,19 +362,43 @@ export const StrategiesList = () => {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
|
const {
|
||||||
useTable(
|
getTableProps,
|
||||||
{
|
getTableBodyProps,
|
||||||
columns: columns as any[], // TODO: fix after `react-table` v8 update
|
headerGroups,
|
||||||
data: data.predefined,
|
rows: standardRows,
|
||||||
initialState,
|
prepareRow,
|
||||||
sortTypes,
|
} = useTable(
|
||||||
autoResetSortBy: false,
|
{
|
||||||
disableSortRemove: true,
|
columns: columns as any[], // TODO: fix after `react-table` v8 update
|
||||||
autoResetHiddenColumns: false,
|
data: data.standard,
|
||||||
},
|
initialState,
|
||||||
useSortBy,
|
sortTypes,
|
||||||
);
|
autoResetSortBy: false,
|
||||||
|
disableSortRemove: true,
|
||||||
|
autoResetHiddenColumns: false,
|
||||||
|
},
|
||||||
|
useSortBy,
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
getTableProps: advancedGetTableProps,
|
||||||
|
getTableBodyProps: advancedGetTableBodyProps,
|
||||||
|
headerGroups: advancedHeaderGroups,
|
||||||
|
rows: advancedRows,
|
||||||
|
prepareRow: advancedPrepareRow,
|
||||||
|
} = useTable(
|
||||||
|
{
|
||||||
|
columns: columns as any[], // TODO: fix after `react-table` v8 update
|
||||||
|
data: data.advanced,
|
||||||
|
initialState,
|
||||||
|
sortTypes,
|
||||||
|
autoResetSortBy: false,
|
||||||
|
disableSortRemove: true,
|
||||||
|
autoResetHiddenColumns: false,
|
||||||
|
},
|
||||||
|
useSortBy,
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getTableProps: customGetTableProps,
|
getTableProps: customGetTableProps,
|
||||||
@ -392,21 +428,25 @@ export const StrategiesList = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledBox>
|
<>
|
||||||
<PageContent
|
<PageContent
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
header={
|
header={
|
||||||
<PageHeader
|
<PageHeader>
|
||||||
titleElement={<PredefinedStrategyTitle />}
|
<Title
|
||||||
title='Strategy types'
|
title='Standard strategies'
|
||||||
/>
|
description='Standard strategies let you enable a feature only for a specified audience. Select a starting setup, then customize your strategy with targeting and variants.'
|
||||||
|
link='https://docs.getunleash.io/reference/activation-strategies'
|
||||||
|
/>
|
||||||
|
</PageHeader>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
|
<RecommendationAlert />
|
||||||
<Table {...getTableProps()}>
|
<Table {...getTableProps()}>
|
||||||
<SortableTableHeader headerGroups={headerGroups} />
|
<SortableTableHeader headerGroups={headerGroups} />
|
||||||
<TableBody {...getTableBodyProps()}>
|
<TableBody {...getTableBodyProps()}>
|
||||||
{rows.map((row) => {
|
{standardRows.map((row) => {
|
||||||
prepareRow(row);
|
prepareRow(row);
|
||||||
const { key, ...rowProps } = row.getRowProps();
|
const { key, ...rowProps } = row.getRowProps();
|
||||||
return (
|
return (
|
||||||
@ -430,7 +470,7 @@ export const StrategiesList = () => {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={rows.length === 0}
|
condition={standardRows.length === 0}
|
||||||
show={
|
show={
|
||||||
<TablePlaceholder>
|
<TablePlaceholder>
|
||||||
No strategies available.
|
No strategies available.
|
||||||
@ -455,11 +495,61 @@ export const StrategiesList = () => {
|
|||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
header={
|
header={
|
||||||
<PageHeader>
|
<PageHeader>
|
||||||
<CustomStrategyTitle />
|
<Title
|
||||||
|
title='Advanced and custom strategies'
|
||||||
|
description='Advanced strategies let you target based on specific properties. Custom activation strategies let you define your own activation strategies to use with Unleash.'
|
||||||
|
link='https://docs.getunleash.io/reference/custom-activation-strategies'
|
||||||
|
/>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
}
|
}
|
||||||
|
sx={{ mt: 2 }}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
|
<StyledSubtitle>
|
||||||
|
<span>Advanced strategies</span>
|
||||||
|
</StyledSubtitle>
|
||||||
|
<Table {...advancedGetTableProps()}>
|
||||||
|
<SortableTableHeader
|
||||||
|
headerGroups={advancedHeaderGroups}
|
||||||
|
/>
|
||||||
|
<TableBody {...advancedGetTableBodyProps()}>
|
||||||
|
{advancedRows.map((row) => {
|
||||||
|
advancedPrepareRow(row);
|
||||||
|
const { key, ...rowProps } = row.getRowProps();
|
||||||
|
return (
|
||||||
|
<TableRow hover key={key} {...rowProps}>
|
||||||
|
{row.cells.map((cell) => {
|
||||||
|
const { key, ...cellProps } =
|
||||||
|
cell.getCellProps();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableCell
|
||||||
|
key={key}
|
||||||
|
{...cellProps}
|
||||||
|
>
|
||||||
|
{cell.render('Cell')}
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={advancedRows.length === 0}
|
||||||
|
show={
|
||||||
|
<TablePlaceholder>
|
||||||
|
No advanced strategies available.
|
||||||
|
</TablePlaceholder>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<StyledSubtitle sx={{ mt: 4 }}>
|
||||||
|
<span>Custom strategies</span>
|
||||||
|
<AddStrategyButton />
|
||||||
|
</StyledSubtitle>
|
||||||
<StrategyDeprecationWarning />
|
<StrategyDeprecationWarning />
|
||||||
<Table {...customGetTableProps()}>
|
<Table {...customGetTableProps()}>
|
||||||
<SortableTableHeader
|
<SortableTableHeader
|
||||||
@ -507,6 +597,6 @@ export const StrategiesList = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</StyledBox>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,13 @@ interface IUseStrategiesOutput {
|
|||||||
error?: Error;
|
error?: Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const STANDARD_STRATEGIES = ['flexibleRollout', 'default'];
|
||||||
|
const mapAdvancedStrategies = (strategies: IStrategy[]): IStrategy[] =>
|
||||||
|
strategies.map((strategy) => ({
|
||||||
|
...strategy,
|
||||||
|
advanced: !STANDARD_STRATEGIES.includes(strategy.name),
|
||||||
|
}));
|
||||||
|
|
||||||
export const useStrategies = (): IUseStrategiesOutput => {
|
export const useStrategies = (): IUseStrategiesOutput => {
|
||||||
const { data, error } = useSWR(STRATEGIES_PATH, fetcher);
|
const { data, error } = useSWR(STRATEGIES_PATH, fetcher);
|
||||||
|
|
||||||
@ -19,7 +26,9 @@ export const useStrategies = (): IUseStrategiesOutput => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
strategies: data?.strategies || defaultStrategies,
|
strategies: mapAdvancedStrategies(
|
||||||
|
data?.strategies || defaultStrategies,
|
||||||
|
),
|
||||||
refetchStrategies,
|
refetchStrategies,
|
||||||
loading: !error && !data,
|
loading: !error && !data,
|
||||||
error,
|
error,
|
||||||
|
@ -41,6 +41,7 @@ export interface IStrategy {
|
|||||||
displayName: string;
|
displayName: string;
|
||||||
editable: boolean;
|
editable: boolean;
|
||||||
deprecated: boolean;
|
deprecated: boolean;
|
||||||
|
advanced?: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
parameters: IStrategyParameter[];
|
parameters: IStrategyParameter[];
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user