2023-05-22 12:46:27 +02:00
import { useState , useMemo , useCallback , FC } from 'react' ;
2022-05-24 10:58:06 +02:00
import { useNavigate } from 'react-router-dom' ;
2023-05-22 12:46:27 +02:00
import { Box , Link , Typography , styled } from '@mui/material' ;
2022-06-14 14:32:16 +02:00
import { Extension } from '@mui/icons-material' ;
2022-05-24 10:58:06 +02:00
import {
Table ,
SortableTableHeader ,
TableBody ,
TableCell ,
TableRow ,
TablePlaceholder ,
} from 'component/common/Table' ;
2022-06-14 14:32:16 +02:00
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell' ;
2022-05-02 12:52:33 +02:00
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender' ;
2022-05-09 14:38:12 +02:00
import { PageContent } from 'component/common/PageContent/PageContent' ;
import { PageHeader } from 'component/common/PageHeader/PageHeader' ;
2022-05-02 12:52:33 +02:00
import { Dialogue } from 'component/common/Dialogue/Dialogue' ;
2022-03-25 12:34:20 +01:00
import { formatStrategyName } from 'utils/strategyNames' ;
2022-03-09 14:59:24 +01:00
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies' ;
2022-03-04 23:39:41 +01:00
import useStrategiesApi from 'hooks/api/actions/useStrategiesApi/useStrategiesApi' ;
import useToast from 'hooks/useToast' ;
2022-03-25 12:34:20 +01:00
import { formatUnknownError } from 'utils/formatUnknownError' ;
2022-05-04 15:16:34 +02:00
import { IStrategy } from 'interfaces/strategy' ;
2022-05-24 10:58:06 +02:00
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell' ;
import { sortTypes } from 'utils/sortTypes' ;
2023-06-14 08:10:04 +02:00
import { useTable , useSortBy } from 'react-table' ;
2022-06-14 14:32:16 +02:00
import { StrategySwitch } from './StrategySwitch/StrategySwitch' ;
import { StrategyEditButton } from './StrategyEditButton/StrategyEditButton' ;
import { StrategyDeleteButton } from './StrategyDeleteButton/StrategyDeleteButton' ;
2022-07-22 09:31:08 +02:00
import { Badge } from 'component/common/Badge/Badge' ;
2023-05-22 12:46:27 +02:00
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon' ;
import { CustomStrategyInfo } from '../CustomStrategyInfo/CustomStrategyInfo' ;
2023-06-14 08:10:04 +02:00
import { AddStrategyButton } from './AddStrategyButton/AddStrategyButton' ;
2022-05-25 10:30:47 +02:00
2022-02-11 00:08:55 +01:00
interface IDialogueMetaData {
show : boolean ;
title : string ;
onConfirm : ( ) = > void ;
}
2021-03-30 15:14:02 +02:00
2023-06-14 08:10:04 +02:00
const StyledBox = styled ( Box ) ( ( { theme } ) = > ( {
display : 'flex' ,
flexDirection : 'column' ,
gap : theme.spacing ( 2 ) ,
} ) ) ;
2022-07-22 09:31:08 +02:00
const StyledBadge = styled ( Badge ) ( ( { theme } ) = > ( {
marginLeft : theme.spacing ( 1 ) ,
display : 'inline-block' ,
} ) ) ;
2023-06-14 08:10:04 +02:00
const StyledTypography = styled ( Typography ) ( ( { theme } ) = > ( {
display : 'flex' ,
fontSize : theme.fontSizes.mainHeader ,
} ) ) ;
2023-05-22 12:46:27 +02:00
const Subtitle : FC < {
title : string ;
description : string ;
link : string ;
} > = ( { title , description , link } ) = > (
2023-06-14 08:10:04 +02:00
< StyledTypography >
2023-05-22 12:46:27 +02:00
{ title }
< HelpIcon
htmlTooltip
tooltip = {
< >
< Typography
2023-10-02 14:25:46 +02:00
variant = 'body2'
component = 'p'
sx = { ( theme ) = > ( { marginBottom : theme.spacing ( 1 ) } ) }
2023-05-22 12:46:27 +02:00
>
{ description }
< / Typography >
2023-10-02 14:25:46 +02:00
< Link href = { link } target = '_blank' variant = 'body2' >
2023-05-22 12:46:27 +02:00
Read more in the documentation
< / Link >
< / >
}
/ >
2023-06-14 08:10:04 +02:00
< / StyledTypography >
2023-05-22 12:46:27 +02:00
) ;
const CustomStrategyTitle : FC = ( ) = > (
< Box
2023-10-02 14:25:46 +02:00
sx = { ( theme ) = > ( {
2023-05-22 12:46:27 +02:00
display : 'flex' ,
flexDirection : 'row' ,
justifyContent : 'space-between' ,
alignItems : 'center' ,
marginBottom : theme.spacing ( 1.5 ) ,
} ) }
>
< Subtitle
2023-10-02 14:25:46 +02:00
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'
2023-05-22 12:46:27 +02:00
/ >
< AddStrategyButton / >
< / Box >
) ;
2023-06-14 08:10:04 +02:00
const PredefinedStrategyTitle = ( ) = > (
< Box >
< Subtitle
2023-10-02 14:25:46 +02:00
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'
2023-06-14 08:10:04 +02:00
/ >
< / Box >
) ;
2022-02-11 00:08:55 +01:00
export const StrategiesList = ( ) = > {
2022-05-05 13:42:18 +02:00
const navigate = useNavigate ( ) ;
2022-02-11 00:08:55 +01:00
const [ dialogueMetaData , setDialogueMetaData ] = useState < IDialogueMetaData > (
2022-02-25 10:55:39 +01:00
{
show : false ,
title : '' ,
onConfirm : ( ) = > { } ,
2023-10-02 14:25:46 +02:00
} ,
2022-02-11 00:08:55 +01:00
) ;
2022-05-24 10:58:06 +02:00
const { strategies , refetchStrategies , loading } = useStrategies ( ) ;
2022-02-11 00:08:55 +01:00
const { removeStrategy , deprecateStrategy , reactivateStrategy } =
useStrategiesApi ( ) ;
const { setToastData , setToastApiError } = useToast ( ) ;
2021-03-30 15:14:02 +02:00
2022-05-24 10:58:06 +02:00
const data = useMemo ( ( ) = > {
if ( loading ) {
2023-05-22 12:46:27 +02:00
const mock = Array ( 5 ) . fill ( {
2022-05-24 10:58:06 +02:00
name : 'Context name' ,
description : 'Context description when loading' ,
} ) ;
2023-05-22 12:46:27 +02:00
return {
all : mock ,
predefined : mock ,
custom : mock ,
} ;
2022-05-24 10:58:06 +02:00
}
2023-05-22 12:46:27 +02:00
const all = strategies . map (
2022-05-24 10:58:06 +02:00
( { name , description , editable , deprecated } ) = > ( {
name ,
description ,
editable ,
deprecated ,
2023-10-02 14:25:46 +02:00
} ) ,
2022-05-24 10:58:06 +02:00
) ;
2023-05-22 12:46:27 +02:00
return {
all ,
2023-10-02 14:25:46 +02:00
predefined : all.filter ( ( strategy ) = > ! strategy . editable ) ,
custom : all.filter ( ( strategy ) = > strategy . editable ) ,
2023-05-22 12:46:27 +02:00
} ;
2022-05-24 10:58:06 +02:00
} , [ strategies , loading ] ) ;
2022-06-14 14:32:16 +02:00
const onToggle = useCallback (
( strategy : IStrategy ) = > ( deprecated : boolean ) = > {
if ( deprecated ) {
setDialogueMetaData ( {
show : true ,
title : 'Really reactivate strategy?' ,
onConfirm : async ( ) = > {
try {
await reactivateStrategy ( strategy ) ;
refetchStrategies ( ) ;
setToastData ( {
type : 'success' ,
title : 'Success' ,
text : 'Strategy reactivated successfully' ,
} ) ;
} catch ( error : unknown ) {
setToastApiError ( formatUnknownError ( error ) ) ;
}
} ,
} ) ;
} else {
setDialogueMetaData ( {
show : true ,
title : 'Really deprecate strategy?' ,
onConfirm : async ( ) = > {
try {
await deprecateStrategy ( strategy ) ;
refetchStrategies ( ) ;
setToastData ( {
type : 'success' ,
title : 'Success' ,
text : 'Strategy deprecated successfully' ,
} ) ;
} catch ( error : unknown ) {
setToastApiError ( formatUnknownError ( error ) ) ;
}
} ,
} ) ;
}
} ,
[
deprecateStrategy ,
reactivateStrategy ,
refetchStrategies ,
setToastApiError ,
setToastData ,
2023-10-02 14:25:46 +02:00
] ,
2022-06-14 14:32:16 +02:00
) ;
const onDeleteStrategy = useCallback (
( strategy : IStrategy ) = > {
setDialogueMetaData ( {
show : true ,
title : 'Really delete strategy?' ,
onConfirm : async ( ) = > {
try {
await removeStrategy ( strategy ) ;
refetchStrategies ( ) ;
setToastData ( {
type : 'success' ,
title : 'Success' ,
text : 'Strategy deleted successfully' ,
} ) ;
} catch ( error : unknown ) {
setToastApiError ( formatUnknownError ( error ) ) ;
}
} ,
} ) ;
} ,
2023-10-02 14:25:46 +02:00
[ removeStrategy , refetchStrategies , setToastApiError , setToastData ] ,
2022-06-14 14:32:16 +02:00
) ;
const onEditStrategy = useCallback (
( strategy : IStrategy ) = > {
navigate ( ` /strategies/ ${ strategy . name } /edit ` ) ;
} ,
2023-10-02 14:25:46 +02:00
[ navigate ] ,
2022-06-14 14:32:16 +02:00
) ;
2022-05-24 10:58:06 +02:00
const columns = useMemo (
( ) = > [
{
id : 'Icon' ,
Cell : ( ) = > (
< Box
data - loading
sx = { {
2022-06-14 14:32:16 +02:00
pl : 3 ,
2022-05-24 10:58:06 +02:00
pr : 1 ,
display : 'flex' ,
alignItems : 'center' ,
} }
>
2023-10-02 14:25:46 +02:00
< Extension color = 'disabled' / >
2022-05-24 10:58:06 +02:00
< / Box >
) ,
} ,
{
2022-10-04 11:41:43 +02:00
id : 'Name' ,
2022-05-24 10:58:06 +02:00
Header : 'Name' ,
2022-10-04 11:41:43 +02:00
accessor : ( row : any ) = > formatStrategyName ( row . name ) ,
2022-05-24 10:58:06 +02:00
width : '90%' ,
Cell : ( {
2024-01-12 10:25:59 +01:00
row : {
original : { name , description , deprecated } ,
} ,
2022-05-24 10:58:06 +02:00
} : any ) = > {
return (
< LinkCell
data - loading
title = { formatStrategyName ( name ) }
2023-05-22 12:46:27 +02:00
subtitle = { description }
2022-05-24 10:58:06 +02:00
to = { ` /strategies/ ${ name } ` }
2021-04-23 15:21:24 +02:00
>
2022-05-24 10:58:06 +02:00
< ConditionallyRender
2023-05-22 12:46:27 +02:00
condition = { deprecated }
2022-05-25 10:30:47 +02:00
show = { ( ) = > (
2023-10-02 14:25:46 +02:00
< StyledBadge color = 'disabled' >
2023-05-22 12:46:27 +02:00
Disabled
2022-07-22 09:31:08 +02:00
< / StyledBadge >
2022-05-25 10:30:47 +02:00
) }
2022-05-24 10:58:06 +02:00
/ >
< / LinkCell >
) ;
} ,
sortType : 'alphanumeric' ,
} ,
{
Header : 'Actions' ,
id : 'Actions' ,
align : 'center' ,
Cell : ( { row : { original } } : any ) = > (
2022-06-14 14:32:16 +02:00
< ActionCell >
< StrategySwitch
deprecated = { original . deprecated }
onToggle = { onToggle ( original ) }
2022-05-24 10:58:06 +02:00
/ >
2023-05-22 12:46:27 +02:00
< ConditionallyRender
condition = { original . editable }
show = {
< >
< ActionCell.Divider / >
< StrategyEditButton
strategy = { original }
onClick = { ( ) = > onEditStrategy ( original ) }
/ >
< StrategyDeleteButton
strategy = { original }
onClick = { ( ) = >
onDeleteStrategy ( original )
}
/ >
< / >
}
2022-06-14 14:32:16 +02:00
/ >
< / ActionCell >
2022-05-24 10:58:06 +02:00
) ,
width : 150 ,
2023-05-22 12:46:27 +02:00
minWidth : 120 ,
2022-05-24 10:58:06 +02:00
disableSortBy : true ,
} ,
{
accessor : 'description' ,
disableSortBy : true ,
} ,
{
accessor : 'sortOrder' ,
sortType : 'number' ,
} ,
] ,
2023-10-02 14:25:46 +02:00
[ onToggle , onEditStrategy , onDeleteStrategy ] ,
2021-03-30 15:14:02 +02:00
) ;
2022-05-24 10:58:06 +02:00
const initialState = useMemo (
( ) = > ( {
2022-10-04 11:41:43 +02:00
sortBy : [ { id : 'Name' , desc : false } ] ,
2022-05-24 10:58:06 +02:00
hiddenColumns : [ 'description' , 'sortOrder' ] ,
} ) ,
2023-10-02 14:25:46 +02:00
[ ] ,
2022-05-24 10:58:06 +02:00
) ;
2023-06-14 08:10:04 +02:00
const { getTableProps , getTableBodyProps , headerGroups , rows , prepareRow } =
useTable (
{
columns : columns as any [ ] , // TODO: fix after `react-table` v8 update
data : data.predefined ,
initialState ,
sortTypes ,
autoResetSortBy : false ,
disableSortRemove : true ,
} ,
2023-10-02 14:25:46 +02:00
useSortBy ,
2023-06-14 08:10:04 +02:00
) ;
2023-05-22 12:46:27 +02:00
const {
getTableProps : customGetTableProps ,
getTableBodyProps : customGetTableBodyProps ,
headerGroups : customHeaderGroups ,
rows : customRows ,
prepareRow : customPrepareRow ,
} = useTable (
{
columns : columns as any [ ] , // TODO: fix after `react-table` v8 update
data : data.custom ,
2022-05-24 10:58:06 +02:00
initialState ,
sortTypes ,
autoResetSortBy : false ,
disableSortRemove : true ,
} ,
2023-10-02 14:25:46 +02:00
useSortBy ,
2021-03-30 15:14:02 +02:00
) ;
2021-05-10 13:22:22 +02:00
const onDialogConfirm = ( ) = > {
dialogueMetaData ? . onConfirm ( ) ;
2022-05-24 10:58:06 +02:00
setDialogueMetaData ( ( prev : IDialogueMetaData ) = > ( {
. . . prev ,
show : false ,
} ) ) ;
2021-05-10 13:22:22 +02:00
} ;
2021-03-30 15:14:02 +02:00
return (
2023-06-14 08:10:04 +02:00
< StyledBox >
< PageContent
isLoading = { loading }
header = {
< PageHeader >
< PredefinedStrategyTitle / >
< / PageHeader >
}
>
< Box >
2023-05-22 12:46:27 +02:00
< Table { ...getTableProps ( ) } >
< SortableTableHeader headerGroups = { headerGroups } / >
< TableBody { ...getTableBodyProps ( ) } >
2023-10-02 14:25:46 +02:00
{ rows . map ( ( row ) = > {
2023-05-22 12:46:27 +02:00
prepareRow ( row ) ;
return (
< TableRow hover { ...row.getRowProps ( ) } >
2023-10-02 14:25:46 +02:00
{ row . cells . map ( ( cell ) = > (
2023-05-22 12:46:27 +02:00
< TableCell { ...cell.getCellProps ( ) } >
{ cell . render ( 'Cell' ) }
< / TableCell >
) ) }
< / TableRow >
) ;
} ) }
< / TableBody >
< / Table >
2022-05-24 10:58:06 +02:00
< ConditionallyRender
2023-05-22 12:46:27 +02:00
condition = { rows . length === 0 }
2022-05-24 10:58:06 +02:00
show = {
2023-06-14 08:10:04 +02:00
< TablePlaceholder >
No strategies available .
< / TablePlaceholder >
2022-05-24 10:58:06 +02:00
}
2023-05-22 12:46:27 +02:00
/ >
< / Box >
2023-06-14 08:10:04 +02:00
< Dialogue
open = { dialogueMetaData . show }
onClick = { onDialogConfirm }
title = { dialogueMetaData ? . title }
onClose = { ( ) = >
setDialogueMetaData ( ( prev : IDialogueMetaData ) = > ( {
. . . prev ,
show : false ,
} ) )
}
/ >
< / PageContent >
< PageContent
isLoading = { loading }
header = {
< PageHeader >
< CustomStrategyTitle / >
< / PageHeader >
}
>
2023-05-22 12:46:27 +02:00
< Box >
< Table { ...customGetTableProps ( ) } >
< SortableTableHeader
headerGroups = { customHeaderGroups }
/ >
< TableBody { ...customGetTableBodyProps ( ) } >
2023-10-02 14:25:46 +02:00
{ customRows . map ( ( row ) = > {
2023-05-22 12:46:27 +02:00
customPrepareRow ( row ) ;
return (
< TableRow hover { ...row.getRowProps ( ) } >
2023-10-02 14:25:46 +02:00
{ row . cells . map ( ( cell ) = > (
2023-05-22 12:46:27 +02:00
< TableCell { ...cell.getCellProps ( ) } >
{ cell . render ( 'Cell' ) }
< / TableCell >
) ) }
< / TableRow >
) ;
} ) }
< / TableBody >
< / Table >
< ConditionallyRender
condition = { customRows . length === 0 }
2023-06-14 08:10:04 +02:00
show = { < CustomStrategyInfo / > }
2022-05-24 10:58:06 +02:00
/ >
2023-05-22 12:46:27 +02:00
< / Box >
2022-05-24 10:58:06 +02:00
2023-06-14 08:10:04 +02:00
< Dialogue
open = { dialogueMetaData . show }
onClick = { onDialogConfirm }
title = { dialogueMetaData ? . title }
onClose = { ( ) = >
setDialogueMetaData ( ( prev : IDialogueMetaData ) = > ( {
. . . prev ,
show : false ,
} ) )
}
/ >
< / PageContent >
< / StyledBox >
2021-03-30 15:14:02 +02:00
) ;
} ;