1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-06 00:07:44 +01:00
unleash.unleash/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx
Nuno Góis df6208e309 feat: add user groups (#1130)
* feat: add user groups table

* add groups and group view

* fix top level await on mock data

* add UG flag

* create group files, refactor group cards

* add generic badge component

* adapt hooks to use endpoints

* implement basic create group

* fix: update snap

* fix: type id as string for now

* implement create group, use api, refactoring

* add stars to group owners

* refactor GroupForm.tsx to use styled components

* feat: remove group

* add edit group

* add group card actions

* feat: edit and remove group users

* add users to groups

* Initial commit

* refine project access table

* add project access group view

* Take users and groups from backend

* Add onsubmit

* new project access, assign and edit

* fix EditGroup, Group

* Finish assigning roles in project

* List assigned projects in group card

* Run prettier

* Add added column to project access table

Co-authored-by: Jaanus Sellin <jaanus@getunleash.ai>
Co-authored-by: sighphyre <liquidwicked64@gmail.com>
2022-07-22 07:31:08 +00:00

360 lines
12 KiB
TypeScript

import { useState, useMemo, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { Box, styled } from '@mui/material';
import { Extension } from '@mui/icons-material';
import {
Table,
SortableTableHeader,
TableBody,
TableCell,
TableRow,
TablePlaceholder,
} from 'component/common/Table';
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import { formatStrategyName } from 'utils/strategyNames';
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
import useStrategiesApi from 'hooks/api/actions/useStrategiesApi/useStrategiesApi';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
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 { StrategySwitch } from './StrategySwitch/StrategySwitch';
import { StrategyEditButton } from './StrategyEditButton/StrategyEditButton';
import { StrategyDeleteButton } from './StrategyDeleteButton/StrategyDeleteButton';
import { Search } from 'component/common/Search/Search';
import { Badge } from 'component/common/Badge/Badge';
interface IDialogueMetaData {
show: boolean;
title: string;
onConfirm: () => void;
}
const StyledBadge = styled(Badge)(({ theme }) => ({
marginLeft: theme.spacing(1),
display: 'inline-block',
}));
export const StrategiesList = () => {
const navigate = useNavigate();
const [dialogueMetaData, setDialogueMetaData] = useState<IDialogueMetaData>(
{
show: false,
title: '',
onConfirm: () => {},
}
);
const { strategies, refetchStrategies, loading } = useStrategies();
const { removeStrategy, deprecateStrategy, reactivateStrategy } =
useStrategiesApi();
const { setToastData, setToastApiError } = useToast();
const data = useMemo(() => {
if (loading) {
return Array(5).fill({
name: 'Context name',
description: 'Context description when loading',
});
}
return strategies.map(
({ name, description, editable, deprecated }) => ({
name,
description,
editable,
deprecated,
})
);
}, [strategies, loading]);
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,
]
);
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));
}
},
});
},
[removeStrategy, refetchStrategies, setToastApiError, setToastData]
);
const onEditStrategy = useCallback(
(strategy: IStrategy) => {
navigate(`/strategies/${strategy.name}/edit`);
},
[navigate]
);
const columns = useMemo(
() => [
{
id: 'Icon',
Cell: () => (
<Box
data-loading
sx={{
pl: 3,
pr: 1,
display: 'flex',
alignItems: 'center',
}}
>
<Extension color="disabled" />
</Box>
),
disableGlobalFilter: true,
},
{
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}`}
>
<ConditionallyRender
condition={!editable}
show={() => (
<StyledBadge color="success">
Predefined
</StyledBadge>
)}
/>
</LinkCell>
);
},
sortType: 'alphanumeric',
},
{
Header: 'Actions',
id: 'Actions',
align: 'center',
Cell: ({ row: { original } }: any) => (
<ActionCell>
<StrategySwitch
deprecated={original.deprecated}
onToggle={onToggle(original)}
disabled={original.name === 'default'}
/>
<ActionCell.Divider />
<StrategyEditButton
strategy={original}
onClick={() => onEditStrategy(original)}
/>
<StrategyDeleteButton
strategy={original}
onClick={() => onDeleteStrategy(original)}
/>
</ActionCell>
),
width: 150,
disableGlobalFilter: true,
disableSortBy: true,
},
{
accessor: 'description',
disableSortBy: true,
},
{
accessor: 'sortOrder',
disableGlobalFilter: true,
sortType: 'number',
},
],
[onToggle, onEditStrategy, onDeleteStrategy]
);
const initialState = useMemo(
() => ({
sortBy: [{ id: 'name', desc: false }],
hiddenColumns: ['description', 'sortOrder'],
}),
[]
);
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 onDialogConfirm = () => {
dialogueMetaData?.onConfirm();
setDialogueMetaData((prev: IDialogueMetaData) => ({
...prev,
show: false,
}));
};
return (
<PageContent
isLoading={loading}
header={
<PageHeader
title="Strategies"
actions={
<>
<Search
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
open={dialogueMetaData.show}
onClick={onDialogConfirm}
title={dialogueMetaData?.title}
onClose={() =>
setDialogueMetaData((prev: IDialogueMetaData) => ({
...prev,
show: false,
}))
}
/>
</PageContent>
);
};