mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-26 01:17:00 +02:00
refactor: port health reports to react-table (#1017)
* refactor: fix table header sort button focus styles * refactor: extract FeatureNameCell component * refactor: port health reports to react-table * refactor: hide columns on small screens * refactor: sort features by name
This commit is contained in:
parent
20d738f725
commit
76ea65b65c
@ -0,0 +1,18 @@
|
||||
import { VFC } from 'react';
|
||||
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
||||
import { IReportTableRow } from 'component/Reporting/ReportTable/ReportTable';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
|
||||
interface IReportExpiredCellProps {
|
||||
row: {
|
||||
original: IReportTableRow;
|
||||
};
|
||||
}
|
||||
|
||||
export const ReportExpiredCell: VFC<IReportExpiredCellProps> = ({ row }) => {
|
||||
if (row.original.expiredAt) {
|
||||
return <DateCell value={row.original.expiredAt} />;
|
||||
}
|
||||
|
||||
return <TextCell>N/A</TextCell>;
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
import { IFeatureToggleListItem } from 'interfaces/featureToggle';
|
||||
import { PERMISSION, KILLSWITCH } from 'constants/featureToggleTypes';
|
||||
import {
|
||||
getDiffInDays,
|
||||
expired,
|
||||
toggleExpiryByTypeMap,
|
||||
} from 'component/Reporting/utils';
|
||||
import { subDays, parseISO } from 'date-fns';
|
||||
|
||||
export const formatExpiredAt = (
|
||||
feature: IFeatureToggleListItem
|
||||
): string | undefined => {
|
||||
const { type, createdAt } = feature;
|
||||
|
||||
if (type === KILLSWITCH || type === PERMISSION) {
|
||||
return;
|
||||
}
|
||||
|
||||
const date = parseISO(createdAt);
|
||||
const now = new Date();
|
||||
const diff = getDiffInDays(date, now);
|
||||
|
||||
if (expired(diff, type)) {
|
||||
const result = diff - toggleExpiryByTypeMap[type];
|
||||
return subDays(now, result).toISOString();
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
import { VFC, ReactElement } from 'react';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { ReportProblemOutlined, Check } from '@mui/icons-material';
|
||||
import { styled } from '@mui/material';
|
||||
import { IReportTableRow } from 'component/Reporting/ReportTable/ReportTable';
|
||||
|
||||
interface IReportStatusCellProps {
|
||||
row: {
|
||||
original: IReportTableRow;
|
||||
};
|
||||
}
|
||||
|
||||
export const ReportStatusCell: VFC<IReportStatusCellProps> = ({
|
||||
row,
|
||||
}): ReactElement => {
|
||||
if (row.original.status === 'potentially-stale') {
|
||||
return (
|
||||
<TextCell>
|
||||
<StyledText>
|
||||
<ReportProblemOutlined />
|
||||
<span>Potentially stale</span>
|
||||
</StyledText>
|
||||
</TextCell>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TextCell>
|
||||
<StyledText>
|
||||
<Check />
|
||||
<span>Healthy</span>
|
||||
</StyledText>
|
||||
</TextCell>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledText = styled('span')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
gap: '1ch',
|
||||
alignItems: 'center',
|
||||
textAlign: 'right',
|
||||
'& svg': { color: theme.palette.inactiveIcon },
|
||||
}));
|
@ -0,0 +1,21 @@
|
||||
import { IFeatureToggleListItem } from 'interfaces/featureToggle';
|
||||
import { getDiffInDays, expired } from 'component/Reporting/utils';
|
||||
import { PERMISSION, KILLSWITCH } from 'constants/featureToggleTypes';
|
||||
import { parseISO } from 'date-fns';
|
||||
|
||||
export type ReportingStatus = 'potentially-stale' | 'healthy';
|
||||
|
||||
export const formatStatus = (
|
||||
feature: IFeatureToggleListItem
|
||||
): ReportingStatus => {
|
||||
const { type, createdAt } = feature;
|
||||
const date = parseISO(createdAt);
|
||||
const now = new Date();
|
||||
const diff = getDiffInDays(date, now);
|
||||
|
||||
if (expired(diff, type) && type !== KILLSWITCH && type !== PERMISSION) {
|
||||
return 'potentially-stale';
|
||||
}
|
||||
|
||||
return 'healthy';
|
||||
};
|
192
frontend/src/component/Reporting/ReportTable/ReportTable.tsx
Normal file
192
frontend/src/component/Reporting/ReportTable/ReportTable.tsx
Normal file
@ -0,0 +1,192 @@
|
||||
import { IFeatureToggleListItem } from 'interfaces/featureToggle';
|
||||
import {
|
||||
TableSearch,
|
||||
SortableTableHeader,
|
||||
TableCell,
|
||||
} from 'component/common/Table';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { sortTypes } from 'utils/sortTypes';
|
||||
import { useSortBy, useGlobalFilter, useTable } from 'react-table';
|
||||
import { Table, TableBody, TableRow, useMediaQuery } from '@mui/material';
|
||||
import { FeatureSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureSeenCell';
|
||||
import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell';
|
||||
import { FeatureNameCell } from 'component/common/Table/cells/FeatureNameCell/FeatureNameCell';
|
||||
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
||||
import { ReportExpiredCell } from 'component/Reporting/ReportExpiredCell/ReportExpiredCell';
|
||||
import { ReportStatusCell } from 'component/Reporting/ReportStatusCell/ReportStatusCell';
|
||||
import { useMemo, useEffect } from 'react';
|
||||
import {
|
||||
formatStatus,
|
||||
ReportingStatus,
|
||||
} from 'component/Reporting/ReportStatusCell/formatStatus';
|
||||
import { formatExpiredAt } from 'component/Reporting/ReportExpiredCell/formatExpiredAt';
|
||||
import { FeatureStaleCell } from 'component/feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell';
|
||||
import theme from 'themes/theme';
|
||||
|
||||
interface IReportTableProps {
|
||||
projectId: string;
|
||||
features: IFeatureToggleListItem[];
|
||||
}
|
||||
|
||||
export interface IReportTableRow {
|
||||
project: string;
|
||||
name: string;
|
||||
type: string;
|
||||
stale?: boolean;
|
||||
status: ReportingStatus;
|
||||
lastSeenAt?: string;
|
||||
createdAt: string;
|
||||
expiredAt?: string;
|
||||
}
|
||||
|
||||
export const ReportTable = ({ projectId, features }: IReportTableProps) => {
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
|
||||
const data: IReportTableRow[] = useMemo(() => {
|
||||
return features.map(feature => {
|
||||
return createReportTableRow(projectId, feature);
|
||||
});
|
||||
}, [projectId, features]);
|
||||
|
||||
const initialState = useMemo(
|
||||
() => ({
|
||||
hiddenColumns: ['description'],
|
||||
sortBy: [{ id: 'name' }],
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
state: { globalFilter },
|
||||
setGlobalFilter,
|
||||
setHiddenColumns,
|
||||
} = useTable(
|
||||
{
|
||||
columns: COLUMNS as any,
|
||||
data: data as any,
|
||||
initialState,
|
||||
sortTypes,
|
||||
autoResetGlobalFilter: false,
|
||||
autoResetSortBy: false,
|
||||
disableSortRemove: true,
|
||||
},
|
||||
useGlobalFilter,
|
||||
useSortBy
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSmallScreen) {
|
||||
setHiddenColumns(['createdAt', 'expiredAt', 'description']);
|
||||
} else {
|
||||
setHiddenColumns(['description']);
|
||||
}
|
||||
}, [setHiddenColumns, isSmallScreen]);
|
||||
|
||||
const header = (
|
||||
<PageHeader
|
||||
title="Overview"
|
||||
actions={
|
||||
<TableSearch
|
||||
initialValue={globalFilter}
|
||||
onChange={setGlobalFilter}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContent header={header}>
|
||||
<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>
|
||||
</PageContent>
|
||||
);
|
||||
};
|
||||
|
||||
const createReportTableRow = (
|
||||
projectId: string,
|
||||
report: IFeatureToggleListItem
|
||||
): IReportTableRow => {
|
||||
return {
|
||||
project: projectId,
|
||||
name: report.name,
|
||||
type: report.type,
|
||||
stale: report.stale,
|
||||
status: formatStatus(report),
|
||||
lastSeenAt: report.lastSeenAt,
|
||||
createdAt: report.createdAt,
|
||||
expiredAt: formatExpiredAt(report),
|
||||
};
|
||||
};
|
||||
|
||||
const COLUMNS = [
|
||||
{
|
||||
Header: 'Seen',
|
||||
accessor: 'lastSeenAt',
|
||||
sortType: 'date',
|
||||
align: 'center',
|
||||
Cell: FeatureSeenCell,
|
||||
},
|
||||
{
|
||||
Header: 'Type',
|
||||
accessor: 'type',
|
||||
align: 'center',
|
||||
Cell: FeatureTypeCell,
|
||||
},
|
||||
{
|
||||
Header: 'Feature toggle name',
|
||||
accessor: 'name',
|
||||
width: '60%',
|
||||
sortType: 'alphanumeric',
|
||||
Cell: FeatureNameCell,
|
||||
},
|
||||
{
|
||||
Header: 'Created on',
|
||||
accessor: 'createdAt',
|
||||
sortType: 'date',
|
||||
Cell: DateCell,
|
||||
},
|
||||
{
|
||||
Header: 'Expired',
|
||||
accessor: 'expiredAt',
|
||||
Cell: ReportExpiredCell,
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
align: 'right',
|
||||
Cell: ReportStatusCell,
|
||||
},
|
||||
{
|
||||
Header: 'State',
|
||||
accessor: 'stale',
|
||||
sortType: 'boolean',
|
||||
Cell: FeatureStaleCell,
|
||||
},
|
||||
{
|
||||
accessor: 'description',
|
||||
},
|
||||
];
|
@ -1,91 +0,0 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
reportToggleList: {
|
||||
width: '100%',
|
||||
margin: 'var(--card-margin-y) 0',
|
||||
borderRadius: 10,
|
||||
boxShadow: 'none',
|
||||
},
|
||||
bulkAction: {
|
||||
backgroundColor: '#f2f2f2',
|
||||
fontSize: 'var(--p-size)',
|
||||
},
|
||||
sortIcon: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
reportToggleListHeader: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
borderBottom: '1px solid #f1f1f1',
|
||||
padding: '1rem var(--card-padding-x)',
|
||||
},
|
||||
reportToggleListInnerContainer: {
|
||||
padding: 'var(--card-padding)',
|
||||
},
|
||||
reportToggleListHeading: {
|
||||
fontSize: 'var(--h1-size)',
|
||||
margin: 0,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
reportIcon: {
|
||||
fontsize: '1.5rem',
|
||||
marginRight: 5,
|
||||
},
|
||||
reportingToggleTable: {
|
||||
width: ' 100%',
|
||||
borderSpacing: '0 0.8rem',
|
||||
'& th': {
|
||||
textAlign: 'left',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
expired: {
|
||||
color: 'var(--danger)',
|
||||
},
|
||||
active: {
|
||||
color: 'var(--success)',
|
||||
},
|
||||
stale: {
|
||||
color: 'var(--danger)',
|
||||
},
|
||||
reportStatus: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
tableRow: {
|
||||
'&:hover': {
|
||||
backgroundColor: '#eeeeee',
|
||||
},
|
||||
},
|
||||
checkbox: {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
},
|
||||
link: {
|
||||
color: theme.palette.primary.main,
|
||||
textDecoration: 'none',
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
},
|
||||
hideColumn: {
|
||||
[theme.breakpoints.down(800)]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
th: {
|
||||
[theme.breakpoints.down(800)]: {
|
||||
minWidth: '120px',
|
||||
},
|
||||
},
|
||||
hideColumnStatus: {
|
||||
[theme.breakpoints.down(550)]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
hideColumnLastSeen: {
|
||||
[theme.breakpoints.down(425)]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
}));
|
@ -1,110 +0,0 @@
|
||||
import { useState, useEffect, VFC } from 'react';
|
||||
import { Paper, MenuItem } from '@mui/material';
|
||||
import { useFeaturesSort } from 'hooks/useFeaturesSort';
|
||||
import { IFeatureToggleListItem } from 'interfaces/featureToggle';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import DropdownMenu from 'component/common/DropdownMenu/DropdownMenu';
|
||||
import {
|
||||
getObjectProperties,
|
||||
getCheckedState,
|
||||
applyCheckedToFeatures,
|
||||
} from '../utils';
|
||||
import { ReportToggleListItem } from './ReportToggleListItem/ReportToggleListItem';
|
||||
import { ReportToggleListHeader } from './ReportToggleListHeader/ReportToggleListHeader';
|
||||
import { useStyles } from './ReportToggleList.styles';
|
||||
|
||||
/* FLAG TO TOGGLE UNFINISHED BULK ACTIONS FEATURE */
|
||||
const BULK_ACTIONS_ON = false;
|
||||
|
||||
interface IReportToggleListProps {
|
||||
selectedProject: string;
|
||||
features: IFeatureToggleListItem[];
|
||||
}
|
||||
|
||||
export const ReportToggleList: VFC<IReportToggleListProps> = ({
|
||||
features,
|
||||
selectedProject,
|
||||
}) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const [checkAll, setCheckAll] = useState(false);
|
||||
const [localFeatures, setFeatures] = useState<IFeatureToggleListItem[]>([]);
|
||||
// @ts-expect-error
|
||||
const { setSort, sorted } = useFeaturesSort(localFeatures);
|
||||
|
||||
useEffect(() => {
|
||||
const formattedFeatures = features.map(feature => ({
|
||||
...getObjectProperties(
|
||||
feature,
|
||||
'name',
|
||||
'lastSeenAt',
|
||||
'createdAt',
|
||||
'stale',
|
||||
'type'
|
||||
),
|
||||
// @ts-expect-error
|
||||
checked: getCheckedState(feature.name, features),
|
||||
setFeatures,
|
||||
}));
|
||||
|
||||
// @ts-expect-error
|
||||
setFeatures(formattedFeatures);
|
||||
}, [features, selectedProject]);
|
||||
|
||||
const handleCheckAll = () => {
|
||||
if (!checkAll) {
|
||||
setCheckAll(true);
|
||||
return setFeatures(prev => applyCheckedToFeatures(prev, true));
|
||||
}
|
||||
setCheckAll(false);
|
||||
return setFeatures(prev => applyCheckedToFeatures(prev, false));
|
||||
};
|
||||
|
||||
const renderListRows = () =>
|
||||
sorted.map(feature => (
|
||||
// @ts-expect-error
|
||||
<ReportToggleListItem
|
||||
key={feature.name}
|
||||
{...feature}
|
||||
project={selectedProject}
|
||||
bulkActionsOn={BULK_ACTIONS_ON}
|
||||
/>
|
||||
));
|
||||
|
||||
const renderBulkActionsMenu = () => (
|
||||
<DropdownMenu
|
||||
id="bulk-actions"
|
||||
label="Bulk actions"
|
||||
renderOptions={() => (
|
||||
<>
|
||||
<MenuItem>Mark toggles as stale</MenuItem>
|
||||
<MenuItem>Delete toggles</MenuItem>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Paper className={styles.reportToggleList}>
|
||||
<div className={styles.reportToggleListHeader}>
|
||||
<h3 className={styles.reportToggleListHeading}>Overview</h3>
|
||||
<ConditionallyRender
|
||||
condition={BULK_ACTIONS_ON}
|
||||
show={renderBulkActionsMenu}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.reportToggleListInnerContainer}>
|
||||
<table className={styles.reportingToggleTable}>
|
||||
<ReportToggleListHeader
|
||||
handleCheckAll={handleCheckAll}
|
||||
checkAll={checkAll}
|
||||
// @ts-expect-error
|
||||
setSort={setSort}
|
||||
bulkActionsOn={BULK_ACTIONS_ON}
|
||||
/>
|
||||
|
||||
<tbody>{renderListRows()}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
};
|
@ -1,104 +0,0 @@
|
||||
import { Dispatch, SetStateAction, VFC } from 'react';
|
||||
import { Checkbox } from '@mui/material';
|
||||
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { ReportingSortType } from 'component/Reporting/constants';
|
||||
import { useStyles } from '../ReportToggleList.styles';
|
||||
|
||||
interface IReportToggleListHeaderProps {
|
||||
checkAll: boolean;
|
||||
setSort: Dispatch<
|
||||
SetStateAction<{ type: ReportingSortType; desc?: boolean }>
|
||||
>;
|
||||
bulkActionsOn: boolean;
|
||||
handleCheckAll: () => void;
|
||||
}
|
||||
|
||||
export const ReportToggleListHeader: VFC<IReportToggleListHeaderProps> = ({
|
||||
handleCheckAll,
|
||||
checkAll,
|
||||
setSort,
|
||||
bulkActionsOn,
|
||||
}) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const handleSort = (type: ReportingSortType) => {
|
||||
setSort(prev => ({
|
||||
type,
|
||||
desc: !prev.desc,
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<thead>
|
||||
<tr>
|
||||
<ConditionallyRender
|
||||
condition={bulkActionsOn}
|
||||
show={
|
||||
<th>
|
||||
<Checkbox
|
||||
onChange={handleCheckAll}
|
||||
value={checkAll}
|
||||
checked={checkAll}
|
||||
className={styles.checkbox}
|
||||
/>
|
||||
</th>
|
||||
}
|
||||
/>
|
||||
|
||||
<th
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
style={{ width: '150px' }}
|
||||
onClick={() => handleSort('name')}
|
||||
>
|
||||
Name
|
||||
<UnfoldMoreOutlinedIcon className={styles.sortIcon} />
|
||||
</th>
|
||||
<th
|
||||
role="button"
|
||||
className={styles.hideColumnLastSeen}
|
||||
tabIndex={0}
|
||||
onClick={() => handleSort('last-seen')}
|
||||
>
|
||||
Last seen
|
||||
<UnfoldMoreOutlinedIcon className={styles.sortIcon} />
|
||||
</th>
|
||||
<th
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={styles.hideColumn}
|
||||
onClick={() => handleSort('created')}
|
||||
>
|
||||
Created
|
||||
<UnfoldMoreOutlinedIcon className={styles.sortIcon} />
|
||||
</th>
|
||||
<th
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={styles.hideColumn}
|
||||
onClick={() => handleSort('expired')}
|
||||
>
|
||||
Expired
|
||||
<UnfoldMoreOutlinedIcon className={styles.sortIcon} />
|
||||
</th>
|
||||
<th
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={styles.hideColumnStatus}
|
||||
onClick={() => handleSort('status')}
|
||||
>
|
||||
Status
|
||||
<UnfoldMoreOutlinedIcon className={styles.sortIcon} />
|
||||
</th>
|
||||
<th
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => handleSort('expired')}
|
||||
>
|
||||
Report
|
||||
<UnfoldMoreOutlinedIcon className={styles.sortIcon} />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
);
|
||||
};
|
@ -1,173 +0,0 @@
|
||||
import { memo, ReactNode } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Checkbox } from '@mui/material';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import ReportProblemOutlinedIcon from '@mui/icons-material/ReportProblemOutlined';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import FeatureStatus from 'component/feature/FeatureView/FeatureStatus/FeatureStatus';
|
||||
import {
|
||||
pluralize,
|
||||
getDates,
|
||||
expired,
|
||||
toggleExpiryByTypeMap,
|
||||
getDiffInDays,
|
||||
} from 'component/Reporting/utils';
|
||||
import { KILLSWITCH, PERMISSION } from 'constants/featureToggleTypes';
|
||||
import { useStyles } from '../ReportToggleList.styles';
|
||||
import { getTogglePath } from 'utils/routePathHelpers';
|
||||
|
||||
interface IReportToggleListItemProps {
|
||||
name: string;
|
||||
stale: boolean;
|
||||
project: string;
|
||||
lastSeenAt?: string;
|
||||
createdAt: string;
|
||||
type: string;
|
||||
checked: boolean;
|
||||
bulkActionsOn: boolean;
|
||||
setFeatures: () => void;
|
||||
}
|
||||
|
||||
export const ReportToggleListItem = memo<IReportToggleListItemProps>(
|
||||
({
|
||||
name,
|
||||
stale,
|
||||
lastSeenAt,
|
||||
createdAt,
|
||||
project,
|
||||
type,
|
||||
checked,
|
||||
bulkActionsOn,
|
||||
setFeatures,
|
||||
}) => {
|
||||
const { classes: styles } = useStyles();
|
||||
const nameMatches = (feature: { name: string }) =>
|
||||
feature.name === name;
|
||||
|
||||
const handleChange = () => {
|
||||
// @ts-expect-error
|
||||
setFeatures(prevState => {
|
||||
const newState = [...prevState];
|
||||
|
||||
return newState.map(feature => {
|
||||
if (nameMatches(feature)) {
|
||||
return { ...feature, checked: !feature.checked };
|
||||
}
|
||||
return feature;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const formatCreatedAt = () => {
|
||||
const [date, now] = getDates(createdAt);
|
||||
|
||||
const diff = getDiffInDays(date, now);
|
||||
if (diff === 0) return '1 day';
|
||||
|
||||
const formatted = pluralize(diff, 'day');
|
||||
|
||||
return `${formatted} ago`;
|
||||
};
|
||||
|
||||
const formatExpiredAt = () => {
|
||||
if (type === KILLSWITCH || type === PERMISSION) {
|
||||
return 'N/A';
|
||||
}
|
||||
|
||||
const [date, now] = getDates(createdAt);
|
||||
const diff = getDiffInDays(date, now);
|
||||
|
||||
if (expired(diff, type)) {
|
||||
const result = diff - toggleExpiryByTypeMap[type];
|
||||
if (result === 0) return '1 day';
|
||||
|
||||
return pluralize(result, 'day');
|
||||
}
|
||||
return 'N/A';
|
||||
};
|
||||
|
||||
const formatLastSeenAt = () => {
|
||||
return (
|
||||
<FeatureStatus
|
||||
lastSeenAt={lastSeenAt}
|
||||
tooltipPlacement="bottom"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderStatus = (icon: ReactNode, text: ReactNode) => (
|
||||
<span className={styles.reportStatus}>
|
||||
{icon}
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
|
||||
const formatReportStatus = () => {
|
||||
if (type === KILLSWITCH || type === PERMISSION) {
|
||||
return renderStatus(
|
||||
<CheckIcon className={styles.reportIcon} />,
|
||||
'Healthy'
|
||||
);
|
||||
}
|
||||
|
||||
const [date, now] = getDates(createdAt);
|
||||
const diff = getDiffInDays(date, now);
|
||||
|
||||
if (expired(diff, type)) {
|
||||
return renderStatus(
|
||||
<ReportProblemOutlinedIcon className={styles.reportIcon} />,
|
||||
'Potentially stale'
|
||||
);
|
||||
}
|
||||
|
||||
return renderStatus(
|
||||
<CheckIcon className={styles.reportIcon} />,
|
||||
'Healthy'
|
||||
);
|
||||
};
|
||||
|
||||
const statusClasses = classnames(
|
||||
styles.active,
|
||||
styles.hideColumnStatus,
|
||||
{
|
||||
[styles.stale]: stale,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<tr className={styles.tableRow}>
|
||||
<ConditionallyRender
|
||||
condition={bulkActionsOn}
|
||||
show={
|
||||
<td>
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
value={checked}
|
||||
onChange={handleChange}
|
||||
className={styles.checkbox}
|
||||
/>
|
||||
</td>
|
||||
}
|
||||
/>
|
||||
<td>
|
||||
<Link
|
||||
to={getTogglePath(project, name)}
|
||||
className={styles.link}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
</td>
|
||||
<td className={styles.hideColumnLastSeen}>
|
||||
{formatLastSeenAt()}
|
||||
</td>
|
||||
<td className={styles.hideColumn}>{formatCreatedAt()}</td>
|
||||
<td className={`${styles.expired} ${styles.hideColumn}`}>
|
||||
{formatExpiredAt()}
|
||||
</td>
|
||||
<td className={statusClasses}>{stale ? 'Stale' : 'Active'}</td>
|
||||
<td>{formatReportStatus()}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
);
|
@ -1,7 +0,0 @@
|
||||
export type ReportingSortType =
|
||||
| 'name'
|
||||
| 'last-seen'
|
||||
| 'created'
|
||||
| 'expired'
|
||||
| 'status'
|
||||
| 'report';
|
@ -1,130 +0,0 @@
|
||||
import {
|
||||
sortFeaturesByNameAscending,
|
||||
sortFeaturesByNameDescending,
|
||||
sortFeaturesByLastSeenAscending,
|
||||
sortFeaturesByLastSeenDescending,
|
||||
sortFeaturesByCreatedAtAscending,
|
||||
sortFeaturesByCreatedAtDescending,
|
||||
sortFeaturesByExpiredAtAscending,
|
||||
sortFeaturesByExpiredAtDescending,
|
||||
sortFeaturesByStatusAscending,
|
||||
sortFeaturesByStatusDescending,
|
||||
} from 'component/Reporting/utils';
|
||||
import { IFeatureToggleListItem } from 'interfaces/featureToggle';
|
||||
|
||||
const getTestData = (): IFeatureToggleListItem[] => [
|
||||
{
|
||||
name: 'abe',
|
||||
createdAt: '2021-02-14T02:42:34.515Z',
|
||||
lastSeenAt: '2021-02-21T19:34:21.830Z',
|
||||
type: 'release',
|
||||
stale: false,
|
||||
environments: [],
|
||||
},
|
||||
{
|
||||
name: 'bet',
|
||||
createdAt: '2021-02-13T02:42:34.515Z',
|
||||
lastSeenAt: '2021-02-19T19:34:21.830Z',
|
||||
type: 'release',
|
||||
stale: false,
|
||||
environments: [],
|
||||
},
|
||||
{
|
||||
name: 'cat',
|
||||
createdAt: '2021-02-12T02:42:34.515Z',
|
||||
lastSeenAt: '2021-02-18T19:34:21.830Z',
|
||||
type: 'experiment',
|
||||
stale: true,
|
||||
environments: [],
|
||||
},
|
||||
];
|
||||
|
||||
test('it sorts features by name ascending', () => {
|
||||
const testData = getTestData();
|
||||
|
||||
const result = sortFeaturesByNameAscending(testData);
|
||||
|
||||
expect(result[0].name).toBe('abe');
|
||||
expect(result[2].name).toBe('cat');
|
||||
});
|
||||
|
||||
test('it sorts features by name descending', () => {
|
||||
const testData = getTestData();
|
||||
|
||||
const result = sortFeaturesByNameDescending(testData);
|
||||
|
||||
expect(result[0].name).toBe('cat');
|
||||
expect(result[2].name).toBe('abe');
|
||||
});
|
||||
|
||||
test('it sorts features by lastSeenAt ascending', () => {
|
||||
const testData = getTestData();
|
||||
|
||||
const result = sortFeaturesByLastSeenAscending(testData);
|
||||
|
||||
expect(result[0].name).toBe('cat');
|
||||
expect(result[2].name).toBe('abe');
|
||||
});
|
||||
|
||||
test('it sorts features by lastSeenAt descending', () => {
|
||||
const testData = getTestData();
|
||||
|
||||
const result = sortFeaturesByLastSeenDescending(testData);
|
||||
|
||||
expect(result[0].name).toBe('abe');
|
||||
expect(result[2].name).toBe('cat');
|
||||
});
|
||||
|
||||
test('it sorts features by createdAt ascending', () => {
|
||||
const testData = getTestData();
|
||||
|
||||
const result = sortFeaturesByCreatedAtAscending(testData);
|
||||
|
||||
expect(result[0].name).toBe('cat');
|
||||
expect(result[2].name).toBe('abe');
|
||||
});
|
||||
|
||||
test('it sorts features by createdAt descending', () => {
|
||||
const testData = getTestData();
|
||||
|
||||
const result = sortFeaturesByCreatedAtDescending(testData);
|
||||
|
||||
expect(result[0].name).toBe('abe');
|
||||
expect(result[2].name).toBe('cat');
|
||||
});
|
||||
|
||||
test('it sorts features by expired ascending', () => {
|
||||
const testData = getTestData();
|
||||
|
||||
const result = sortFeaturesByExpiredAtAscending(testData);
|
||||
|
||||
expect(result[0].name).toBe('cat');
|
||||
expect(result[2].name).toBe('abe');
|
||||
});
|
||||
|
||||
test('it sorts features by expired descending', () => {
|
||||
const testData = getTestData();
|
||||
|
||||
const result = sortFeaturesByExpiredAtDescending(testData);
|
||||
|
||||
expect(result[0].name).toBe('abe');
|
||||
expect(result[2].name).toBe('cat');
|
||||
});
|
||||
|
||||
test('it sorts features by status ascending', () => {
|
||||
const testData = getTestData();
|
||||
|
||||
const result = sortFeaturesByStatusAscending(testData);
|
||||
|
||||
expect(result[0].name).toBe('abe');
|
||||
expect(result[2].name).toBe('cat');
|
||||
});
|
||||
|
||||
test('it sorts features by status descending', () => {
|
||||
const testData = getTestData();
|
||||
|
||||
const result = sortFeaturesByStatusDescending(testData);
|
||||
|
||||
expect(result[0].name).toBe('cat');
|
||||
expect(result[2].name).toBe('abe');
|
||||
});
|
@ -1,10 +1,6 @@
|
||||
import parseISO from 'date-fns/parseISO';
|
||||
import differenceInDays from 'date-fns/differenceInDays';
|
||||
|
||||
import { EXPERIMENT, OPERATIONAL, RELEASE } from 'constants/featureToggleTypes';
|
||||
|
||||
import { IFeatureToggleListItem } from 'interfaces/featureToggle';
|
||||
|
||||
const FORTY_DAYS = 40;
|
||||
const SEVEN_DAYS = 7;
|
||||
|
||||
@ -14,197 +10,10 @@ export const toggleExpiryByTypeMap: Record<string, number> = {
|
||||
[OPERATIONAL]: SEVEN_DAYS,
|
||||
};
|
||||
|
||||
export interface IFeatureToggleListItemCheck extends IFeatureToggleListItem {
|
||||
checked: boolean;
|
||||
}
|
||||
|
||||
export const applyCheckedToFeatures = (
|
||||
features: IFeatureToggleListItem[],
|
||||
checkedState: boolean
|
||||
): IFeatureToggleListItemCheck[] => {
|
||||
return features.map(feature => ({
|
||||
...feature,
|
||||
checked: checkedState,
|
||||
}));
|
||||
export const getDiffInDays = (date: Date, now: Date) => {
|
||||
return Math.abs(differenceInDays(date, now));
|
||||
};
|
||||
|
||||
export const getCheckedState = (
|
||||
name: string,
|
||||
features: IFeatureToggleListItemCheck[]
|
||||
) => {
|
||||
const feature = features.find(feature => feature.name === name);
|
||||
|
||||
if (feature) {
|
||||
return feature.checked;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getDiffInDays = (date: Date, now: Date) =>
|
||||
Math.abs(differenceInDays(date, now));
|
||||
|
||||
export const expired = (diff: number, type: string) => {
|
||||
if (diff >= toggleExpiryByTypeMap[type]) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getObjectProperties = <T extends object>(
|
||||
target: T,
|
||||
...keys: (keyof T)[]
|
||||
): Partial<T> => {
|
||||
const newObject: Partial<T> = {};
|
||||
|
||||
keys.forEach(key => {
|
||||
if (target[key] !== undefined) {
|
||||
newObject[key] = target[key];
|
||||
}
|
||||
});
|
||||
|
||||
return newObject;
|
||||
};
|
||||
|
||||
export const sortFeaturesByNameAscending = (
|
||||
features: IFeatureToggleListItem[]
|
||||
): IFeatureToggleListItem[] => {
|
||||
const sorted = [...features];
|
||||
sorted.sort((a, b) => {
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return sorted;
|
||||
};
|
||||
|
||||
export const sortFeaturesByNameDescending = (
|
||||
features: IFeatureToggleListItem[]
|
||||
): IFeatureToggleListItem[] =>
|
||||
sortFeaturesByNameAscending([...features]).reverse();
|
||||
|
||||
export const sortFeaturesByLastSeenAscending = (
|
||||
features: IFeatureToggleListItem[]
|
||||
): IFeatureToggleListItem[] => {
|
||||
const sorted = [...features];
|
||||
sorted.sort((a, b) => {
|
||||
if (!a.lastSeenAt) return -1;
|
||||
if (!b.lastSeenAt) return 1;
|
||||
|
||||
const dateA = parseISO(a.lastSeenAt);
|
||||
const dateB = parseISO(b.lastSeenAt);
|
||||
|
||||
return dateA.getTime() - dateB.getTime();
|
||||
});
|
||||
return sorted;
|
||||
};
|
||||
|
||||
export const sortFeaturesByLastSeenDescending = (
|
||||
features: IFeatureToggleListItem[]
|
||||
): IFeatureToggleListItem[] =>
|
||||
sortFeaturesByLastSeenAscending([...features]).reverse();
|
||||
|
||||
export const sortFeaturesByCreatedAtAscending = (
|
||||
features: IFeatureToggleListItem[]
|
||||
): IFeatureToggleListItem[] => {
|
||||
const sorted = [...features];
|
||||
sorted.sort((a, b) => {
|
||||
const dateA = parseISO(a.createdAt);
|
||||
const dateB = parseISO(b.createdAt);
|
||||
|
||||
return dateA.getTime() - dateB.getTime();
|
||||
});
|
||||
return sorted;
|
||||
};
|
||||
|
||||
export const sortFeaturesByCreatedAtDescending = (
|
||||
features: IFeatureToggleListItem[]
|
||||
): IFeatureToggleListItem[] =>
|
||||
sortFeaturesByCreatedAtAscending([...features]).reverse();
|
||||
|
||||
export const sortFeaturesByExpiredAtAscending = (
|
||||
features: IFeatureToggleListItem[]
|
||||
): IFeatureToggleListItem[] => {
|
||||
const sorted = [...features];
|
||||
sorted.sort((a, b) => {
|
||||
const now = new Date();
|
||||
const dateA = parseISO(a.createdAt);
|
||||
const dateB = parseISO(b.createdAt);
|
||||
|
||||
const diffA = getDiffInDays(dateA, now);
|
||||
const diffB = getDiffInDays(dateB, now);
|
||||
|
||||
if (!expired(diffA, a.type) && expired(diffB, b.type)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (expired(diffA, a.type) && !expired(diffB, b.type)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const expiredByA = diffA - toggleExpiryByTypeMap[a.type];
|
||||
const expiredByB = diffB - toggleExpiryByTypeMap[b.type];
|
||||
|
||||
return expiredByB - expiredByA;
|
||||
});
|
||||
return sorted;
|
||||
};
|
||||
|
||||
export const sortFeaturesByExpiredAtDescending = (
|
||||
features: IFeatureToggleListItem[]
|
||||
): IFeatureToggleListItem[] => {
|
||||
const sorted = [...features];
|
||||
const now = new Date();
|
||||
sorted.sort((a, b) => {
|
||||
const dateA = parseISO(a.createdAt);
|
||||
const dateB = parseISO(b.createdAt);
|
||||
|
||||
const diffA = getDiffInDays(dateA, now);
|
||||
const diffB = getDiffInDays(dateB, now);
|
||||
|
||||
if (!expired(diffA, a.type) && expired(diffB, b.type)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (expired(diffA, a.type) && !expired(diffB, b.type)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const expiredByA = diffA - toggleExpiryByTypeMap[a.type];
|
||||
const expiredByB = diffB - toggleExpiryByTypeMap[b.type];
|
||||
|
||||
return expiredByA - expiredByB;
|
||||
});
|
||||
return sorted;
|
||||
};
|
||||
|
||||
export const sortFeaturesByStatusAscending = (
|
||||
features: IFeatureToggleListItem[]
|
||||
): IFeatureToggleListItem[] => {
|
||||
const sorted = [...features];
|
||||
sorted.sort((a, b) => {
|
||||
if (a.stale) return 1;
|
||||
if (b.stale) return -1;
|
||||
return 0;
|
||||
});
|
||||
return sorted;
|
||||
};
|
||||
|
||||
export const sortFeaturesByStatusDescending = (
|
||||
features: IFeatureToggleListItem[]
|
||||
): IFeatureToggleListItem[] =>
|
||||
sortFeaturesByStatusAscending([...features]).reverse();
|
||||
|
||||
export const pluralize = (items: number, word: string): string => {
|
||||
if (items === 1) return `${items} ${word}`;
|
||||
return `${items} ${word}s`;
|
||||
};
|
||||
|
||||
export const getDates = (dateString: string): [Date, Date] => {
|
||||
const date = parseISO(dateString);
|
||||
const now = new Date();
|
||||
|
||||
return [date, now];
|
||||
return diff >= toggleExpiryByTypeMap[type];
|
||||
};
|
||||
|
@ -19,6 +19,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
whiteSpace: 'nowrap',
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
':hover, :focus, &:focus-visible, &:active': {
|
||||
outline: 'revert',
|
||||
'.hover-only': {
|
||||
|
@ -0,0 +1,22 @@
|
||||
import { VFC } from 'react';
|
||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||
|
||||
interface IFeatureNameCellProps {
|
||||
row: {
|
||||
original: {
|
||||
name: string;
|
||||
description: string;
|
||||
project: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const FeatureNameCell: VFC<IFeatureNameCellProps> = ({ row }) => {
|
||||
return (
|
||||
<LinkCell
|
||||
title={row.original.name}
|
||||
subtitle={row.original.description}
|
||||
to={`/projects/${row.original.project}/features/${row.original.name}`}
|
||||
/>
|
||||
);
|
||||
};
|
@ -25,6 +25,7 @@ import { useLocalStorage } from 'hooks/useLocalStorage';
|
||||
import { FeatureSchema } from 'openapi';
|
||||
import { CreateFeatureButton } from '../CreateFeatureButton/CreateFeatureButton';
|
||||
import { FeatureStaleCell } from './FeatureStaleCell/FeatureStaleCell';
|
||||
import { FeatureNameCell } from 'component/common/Table/cells/FeatureNameCell/FeatureNameCell';
|
||||
|
||||
const featuresPlaceholder: FeatureSchema[] = Array(15).fill({
|
||||
name: 'Name of the feature',
|
||||
@ -55,18 +56,7 @@ const columns = [
|
||||
accessor: 'name',
|
||||
maxWidth: 300,
|
||||
width: '67%',
|
||||
Cell: ({
|
||||
row: {
|
||||
// @ts-expect-error -- props type
|
||||
original: { name, description, project },
|
||||
},
|
||||
}) => (
|
||||
<LinkCell
|
||||
title={name}
|
||||
subtitle={description}
|
||||
to={`/projects/${project}/features/${name}`}
|
||||
/>
|
||||
),
|
||||
Cell: FeatureNameCell,
|
||||
sortType: 'alphanumeric',
|
||||
},
|
||||
{
|
||||
@ -139,6 +129,7 @@ export const FeatureToggleListTable: VFC = () => {
|
||||
{
|
||||
// @ts-expect-error -- fix in react-table v8
|
||||
columns,
|
||||
// @ts-expect-error -- fix in react-table v8
|
||||
data,
|
||||
initialState,
|
||||
sortTypes,
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useHealthReport } from 'hooks/api/getters/useHealthReport/useHealthReport';
|
||||
import ApiError from 'component/common/ApiError/ApiError';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { ReportToggleList } from 'component/Reporting/ReportToggleList/ReportToggleList';
|
||||
import { ReportCard } from 'component/Reporting/ReportCard/ReportCard';
|
||||
import { usePageTitle } from 'hooks/usePageTitle';
|
||||
import { ReportTable } from 'component/Reporting/ReportTable/ReportTable';
|
||||
|
||||
interface IProjectHealthProps {
|
||||
projectId: string;
|
||||
@ -33,8 +33,8 @@ const ProjectHealth = ({ projectId }: IProjectHealthProps) => {
|
||||
}
|
||||
/>
|
||||
<ReportCard healthReport={healthReport} />
|
||||
<ReportToggleList
|
||||
selectedProject={projectId}
|
||||
<ReportTable
|
||||
projectId={projectId}
|
||||
features={healthReport.features}
|
||||
/>
|
||||
</div>
|
||||
|
@ -150,7 +150,7 @@ exports[`renders an empty list correctly 1`] = `
|
||||
<button
|
||||
aria-label=""
|
||||
aria-labelledby={null}
|
||||
className="tss-14l86a5-sortedButton tss-h39o0j-sortButton"
|
||||
className="tss-14l86a5-sortedButton tss-1inbba3-sortButton"
|
||||
data-mui-internal-clone-element={true}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
@ -185,6 +185,7 @@ const compareNullableDates = (
|
||||
): number => {
|
||||
return a && b ? a?.getTime?.() - b?.getTime?.() : a ? 1 : b ? -1 : 0;
|
||||
};
|
||||
|
||||
const sortByExpired = (
|
||||
features: Readonly<FeatureSchema[]>
|
||||
): FeatureSchema[] => {
|
||||
|
@ -1,91 +0,0 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import {
|
||||
sortFeaturesByNameAscending,
|
||||
sortFeaturesByNameDescending,
|
||||
sortFeaturesByLastSeenAscending,
|
||||
sortFeaturesByLastSeenDescending,
|
||||
sortFeaturesByCreatedAtAscending,
|
||||
sortFeaturesByCreatedAtDescending,
|
||||
sortFeaturesByExpiredAtAscending,
|
||||
sortFeaturesByExpiredAtDescending,
|
||||
sortFeaturesByStatusAscending,
|
||||
sortFeaturesByStatusDescending,
|
||||
} from 'component/Reporting/utils';
|
||||
|
||||
import { ReportingSortType } from 'component/Reporting/constants';
|
||||
import { IFeatureToggleListItem } from 'interfaces/featureToggle';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const useSort = () => {
|
||||
const [sortData, setSortData] = useState<{
|
||||
sortKey: ReportingSortType;
|
||||
ascending: boolean;
|
||||
}>({
|
||||
sortKey: 'name',
|
||||
ascending: true,
|
||||
});
|
||||
|
||||
const handleSortName = (features: IFeatureToggleListItem[]) => {
|
||||
if (sortData.ascending) {
|
||||
return sortFeaturesByNameAscending(features);
|
||||
}
|
||||
|
||||
return sortFeaturesByNameDescending(features);
|
||||
};
|
||||
|
||||
const handleSortLastSeen = (features: IFeatureToggleListItem[]) => {
|
||||
if (sortData.ascending) {
|
||||
return sortFeaturesByLastSeenAscending(features);
|
||||
}
|
||||
return sortFeaturesByLastSeenDescending(features);
|
||||
};
|
||||
|
||||
const handleSortCreatedAt = (features: IFeatureToggleListItem[]) => {
|
||||
if (sortData.ascending) {
|
||||
return sortFeaturesByCreatedAtAscending(features);
|
||||
}
|
||||
return sortFeaturesByCreatedAtDescending(features);
|
||||
};
|
||||
|
||||
const handleSortExpiredAt = (features: IFeatureToggleListItem[]) => {
|
||||
if (sortData.ascending) {
|
||||
return sortFeaturesByExpiredAtAscending(features);
|
||||
}
|
||||
return sortFeaturesByExpiredAtDescending(features);
|
||||
};
|
||||
|
||||
const handleSortStatus = (features: IFeatureToggleListItem[]) => {
|
||||
if (sortData.ascending) {
|
||||
return sortFeaturesByStatusAscending(features);
|
||||
}
|
||||
return sortFeaturesByStatusDescending(features);
|
||||
};
|
||||
|
||||
const sort = useCallback(
|
||||
(features: IFeatureToggleListItem[]): IFeatureToggleListItem[] => {
|
||||
switch (sortData.sortKey) {
|
||||
case 'name':
|
||||
return handleSortName(features);
|
||||
case 'last-seen':
|
||||
return handleSortLastSeen(features);
|
||||
case 'created':
|
||||
return handleSortCreatedAt(features);
|
||||
case 'expired':
|
||||
case 'report':
|
||||
return handleSortExpiredAt(features);
|
||||
case 'status':
|
||||
return handleSortStatus(features);
|
||||
default:
|
||||
return features;
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[sortData]
|
||||
);
|
||||
|
||||
return [sort, setSortData] as const;
|
||||
};
|
||||
|
||||
export default useSort;
|
Loading…
Reference in New Issue
Block a user