1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-04 13:48:56 +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:
Nuno Góis 2025-07-30 09:55:51 +01:00 committed by GitHub
parent 0f565c50e9
commit c5b37fc7c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 173 additions and 73 deletions

View File

@ -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> </>
); );
}; };

View File

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

View File

@ -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[];
} }