diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/DisableEnableStrategyDialog/DisableEnableStrategyDialog.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/DisableEnableStrategyDialog/DisableEnableStrategyDialog.tsx index 1672131e34..5d263342aa 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/DisableEnableStrategyDialog/DisableEnableStrategyDialog.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/MenuStrategyRemove/DisableEnableStrategyDialog/DisableEnableStrategyDialog.tsx @@ -47,7 +47,9 @@ export const DisableEnableStrategyDialog = ({ ? `Add ${ disabled ? 'enable' : 'disable' } strategy to change request?` - : 'Are you sure you want to enable this strategy?' + : `Are you sure you want to ${ + disabled ? 'enable' : 'disable' + } this strategy?` } open={isOpen} primaryButtonText={ diff --git a/frontend/src/component/strategies/CreateStrategy/CreateStrategy.tsx b/frontend/src/component/strategies/CreateStrategy/CreateStrategy.tsx index 3eddca5508..6ce07e1121 100644 --- a/frontend/src/component/strategies/CreateStrategy/CreateStrategy.tsx +++ b/frontend/src/component/strategies/CreateStrategy/CreateStrategy.tsx @@ -10,6 +10,7 @@ import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies'; import { formatUnknownError } from 'utils/formatUnknownError'; import { CreateButton } from 'component/common/CreateButton/CreateButton'; import { GO_BACK } from 'constants/navigate'; +import { CustomStrategyInfo } from '../CustomStrategyInfo/CustomStrategyInfo'; export const CreateStrategy = () => { const { setToastData, setToastApiError } = useToast(); @@ -78,6 +79,7 @@ export const CreateStrategy = () => { documentationLinkLabel="Custom strategies documentation" formatApiCode={formatApiCode} > + ( + ({ + marginBottom: theme.spacing(2), + })} + > + {children} + +); + +export const CustomStrategyInfo: FC<{ alert?: boolean }> = ({ alert }) => { + const content = ( + <> + + We recommend you to use the predefined strategies like Gradual + rollout with constraints instead of creating a custom strategy. + + + If you decide to create a custom strategy be aware of: +
    +
  • + They require writing custom code and deployments for + each SDK you’re using. +
  • +
  • + Differing implementation in each SDK will cause toggles + to evaluate differently +
  • +
  • + Requires a lot of configuration in both Unleash admin UI + and the SDK. +
  • +
+
+ + Constraints don’t have these problems. They’re configured once + in the admin UI and behave in the same way in each SDK without + further configuration. + + + ); + + if (alert) { + return ( + ({ + marginBottom: theme.spacing(3), + })} + > + {content} + + ); + } + + return ( + ({ + maxWidth: '720px', + padding: theme.spacing(4, 2), + margin: '0 auto', + })} + > + {content} + + ); +}; diff --git a/frontend/src/component/strategies/StrategiesList/AddStrategyButton/AddStrategyButton.tsx b/frontend/src/component/strategies/StrategiesList/AddStrategyButton/AddStrategyButton.tsx index e45285b3a4..57971c1174 100644 --- a/frontend/src/component/strategies/StrategiesList/AddStrategyButton/AddStrategyButton.tsx +++ b/frontend/src/component/strategies/StrategiesList/AddStrategyButton/AddStrategyButton.tsx @@ -20,7 +20,7 @@ export const AddStrategyButton = () => { onClick={() => navigate('/strategies/create')} permission={CREATE_STRATEGY} size="large" - tooltipProps={{ title: 'New strategy type' }} + tooltipProps={{ title: 'New custom strategy' }} > @@ -32,7 +32,7 @@ export const AddStrategyButton = () => { permission={CREATE_STRATEGY} data-testid={ADD_NEW_STRATEGY_ID} > - New strategy type + New custom strategy } /> diff --git a/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx b/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx index 7a726349e7..c7736a440f 100644 --- a/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx +++ b/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx @@ -1,6 +1,6 @@ -import { useState, useMemo, useCallback } from 'react'; +import { useState, useMemo, useCallback, FC } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Box, styled } from '@mui/material'; +import { Box, Link, Typography, styled } from '@mui/material'; import { Extension } from '@mui/icons-material'; import { Table, @@ -31,6 +31,8 @@ import { StrategyEditButton } from './StrategyEditButton/StrategyEditButton'; import { StrategyDeleteButton } from './StrategyDeleteButton/StrategyDeleteButton'; import { Search } from 'component/common/Search/Search'; import { Badge } from 'component/common/Badge/Badge'; +import { HelpIcon } from 'component/common/HelpIcon/HelpIcon'; +import { CustomStrategyInfo } from '../CustomStrategyInfo/CustomStrategyInfo'; interface IDialogueMetaData { show: boolean; @@ -43,6 +45,62 @@ const StyledBadge = styled(Badge)(({ theme }) => ({ display: 'inline-block', })); +const Subtitle: FC<{ + title: string; + description: string; + link: string; +}> = ({ title, description, link }) => ( + + {title} + + ({ marginBottom: theme.spacing(1) })} + > + {description} + + + Read more in the documentation + + + } + /> + +); + +const PredefinedStrategyTitle = () => ( + ({ marginBottom: theme.spacing(1.5) })}> + + +); + +const CustomStrategyTitle: FC = () => ( + ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: theme.spacing(1.5), + })} + > + + + +); + export const StrategiesList = () => { const navigate = useNavigate(); const [dialogueMetaData, setDialogueMetaData] = useState( @@ -60,13 +118,18 @@ export const StrategiesList = () => { const data = useMemo(() => { if (loading) { - return Array(5).fill({ + const mock = Array(5).fill({ name: 'Context name', description: 'Context description when loading', }); + return { + all: mock, + predefined: mock, + custom: mock, + }; } - return strategies.map( + const all = strategies.map( ({ name, description, editable, deprecated }) => ({ name, description, @@ -74,6 +137,11 @@ export const StrategiesList = () => { deprecated, }) ); + return { + all, + predefined: all.filter(strategy => !strategy.editable), + custom: all.filter(strategy => strategy.editable), + }; }, [strategies, loading]); const onToggle = useCallback( @@ -181,24 +249,21 @@ export const StrategiesList = () => { width: '90%', Cell: ({ row: { - original: { name, description, deprecated, editable }, + original: { name, description, deprecated }, }, }: any) => { - const subTitleText = deprecated - ? `${description} (deprecated)` - : description; return ( ( - - Predefined + + Disabled )} /> @@ -217,18 +282,28 @@ export const StrategiesList = () => { deprecated={original.deprecated} onToggle={onToggle(original)} /> - - onEditStrategy(original)} - /> - onDeleteStrategy(original)} + + + onEditStrategy(original)} + /> + + onDeleteStrategy(original) + } + /> + + } /> ), width: 150, + minWidth: 120, disableGlobalFilter: true, disableSortBy: true, }, @@ -264,7 +339,28 @@ export const StrategiesList = () => { } = useTable( { columns: columns as any[], // TODO: fix after `react-table` v8 update - data, + data: data.predefined, + initialState, + sortTypes, + autoResetGlobalFilter: false, + autoResetSortBy: false, + disableSortRemove: true, + }, + useGlobalFilter, + useSortBy + ); + + const { + getTableProps: customGetTableProps, + getTableBodyProps: customGetTableBodyProps, + headerGroups: customHeaderGroups, + rows: customRows, + prepareRow: customPrepareRow, + setGlobalFilter: customSetGlobalFilter, + } = useTable( + { + columns: columns as any[], // TODO: fix after `react-table` v8 update + data: data.custom, initialState, sortTypes, autoResetGlobalFilter: false, @@ -292,58 +388,99 @@ export const StrategiesList = () => { - - - - + { + setGlobalFilter(...props); + customSetGlobalFilter(...props); + }} + /> } /> } > - - - - {rows.map(row => { - prepareRow(row); - return ( - - {row.cells.map(cell => ( - - {cell.render('Cell')} - - ))} - - ); - })} - -
-
- ({ paddingBottom: theme.spacing(4) })}> + + + + + {rows.map(row => { + prepareRow(row); + return ( + + {row.cells.map(cell => ( + + {cell.render('Cell')} + + ))} + + ); + })} + +
0} + condition={rows.length === 0} show={ - - No strategies found matching “ - {globalFilter} - ” - - } - elseShow={ - - No strategies available. Get started by adding - one. - + 0} + show={ + + No predefined strategies found matching + “ + {globalFilter} + ” + + } + elseShow={ + + No strategies available. + + } + /> } /> - } - /> + + + + + + + {customRows.map(row => { + customPrepareRow(row); + return ( + + {row.cells.map(cell => ( + + {cell.render('Cell')} + + ))} + + ); + })} + +
+ 0} + show={ + + No custom strategies found matching + “ + {globalFilter} + ” + + } + elseShow={} + /> + } + /> +
+