mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
fix: misc UI improvements (#806)
* fix: link color in project features Co-authored-by: olav <mail@olav.io> * fix: link in health page Co-authored-by: olav <mail@olav.io> * fix: keep sorting state in project toggles list Co-authored-by: olav <mail@olav.io> * fix: style link in the toggle list project Co-authored-by: olav <mail@olav.io> * refactor: update browser list Co-authored-by: olav <mail@olav.io> Co-authored-by: olav <mail@olav.io>
This commit is contained in:
parent
cfc2338e78
commit
4589a19e03
@ -1,29 +1,26 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Paper, MenuItem } from '@material-ui/core';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ReportToggleListItem from './ReportToggleListItem/ReportToggleListItem';
|
||||
import ReportToggleListHeader from './ReportToggleListHeader/ReportToggleListHeader';
|
||||
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
||||
import DropdownMenu from '../../common/DropdownMenu/DropdownMenu';
|
||||
|
||||
import {
|
||||
getObjectProperties,
|
||||
getCheckedState,
|
||||
applyCheckedToFeatures,
|
||||
} from '../utils';
|
||||
|
||||
import useSort from '../../../hooks/useSort';
|
||||
|
||||
import styles from './ReportToggleList.module.scss';
|
||||
import { useStyles } from './ReportToggleList.styles';
|
||||
import { useFeaturesSort } from 'hooks/useFeaturesSort';
|
||||
|
||||
/* FLAG TO TOGGLE UNFINISHED BULK ACTIONS FEATURE */
|
||||
const BULK_ACTIONS_ON = false;
|
||||
|
||||
const ReportToggleList = ({ features, selectedProject }) => {
|
||||
const styles = useStyles();
|
||||
const [checkAll, setCheckAll] = useState(false);
|
||||
const [localFeatures, setFeatures] = useState([]);
|
||||
const [sort, setSortData] = useSort();
|
||||
const { setSort, sorted } = useFeaturesSort(localFeatures);
|
||||
|
||||
useEffect(() => {
|
||||
const formattedFeatures = features.map(feature => ({
|
||||
@ -52,7 +49,7 @@ const ReportToggleList = ({ features, selectedProject }) => {
|
||||
};
|
||||
|
||||
const renderListRows = () =>
|
||||
sort(localFeatures).map(feature => (
|
||||
sorted.map(feature => (
|
||||
<ReportToggleListItem
|
||||
key={feature.name}
|
||||
{...feature}
|
||||
@ -88,7 +85,7 @@ const ReportToggleList = ({ features, selectedProject }) => {
|
||||
<ReportToggleListHeader
|
||||
handleCheckAll={handleCheckAll}
|
||||
checkAll={checkAll}
|
||||
setSortData={setSortData}
|
||||
setSort={setSort}
|
||||
bulkActionsOn={BULK_ACTIONS_ON}
|
||||
/>
|
||||
|
||||
|
@ -1,97 +0,0 @@
|
||||
.reportToggleList {
|
||||
width: 100%;
|
||||
margin: var(--card-margin-y) 0;
|
||||
border-radius: 10px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.bulkAction {
|
||||
background-color: #f2f2f2;
|
||||
font-size: var(--p-size);
|
||||
}
|
||||
|
||||
.sortIcon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.reportToggleListHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
padding: 1rem var(--card-padding-x);
|
||||
}
|
||||
|
||||
.reportToggleListInnerContainer {
|
||||
padding: var(--card-padding);
|
||||
}
|
||||
|
||||
.reportToggleListHeading {
|
||||
font-size: var(--h1-size);
|
||||
margin: 0;
|
||||
font-weight: 'bold';
|
||||
}
|
||||
|
||||
.reportingToggleTable {
|
||||
width: 100%;
|
||||
border-spacing: 0 0.8rem;
|
||||
}
|
||||
|
||||
.reportingToggleTable th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.expired {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.active {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.stale {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.reportStatus {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.reportIcon {
|
||||
font-size: 1.5rem;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.tableRow {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tableRow:hover {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.hideColumn {
|
||||
display: none;
|
||||
}
|
||||
th {
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 550px) {
|
||||
.hideColumnStatus {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 425px) {
|
||||
.hideColumnLastSeen {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
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,
|
||||
},
|
||||
|
||||
[theme.breakpoints.down(800)]: {
|
||||
hideColumn: {
|
||||
display: 'none',
|
||||
},
|
||||
th: {
|
||||
minWidth: '120px',
|
||||
},
|
||||
},
|
||||
|
||||
[theme.breakpoints.down(550)]: {
|
||||
hideColumnStatus: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
|
||||
[theme.breakpoints.down(425)]: {
|
||||
hideColumnLastSeen: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
}));
|
@ -5,27 +5,21 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
import {
|
||||
NAME,
|
||||
LAST_SEEN,
|
||||
CREATED,
|
||||
EXPIRED,
|
||||
STATUS,
|
||||
REPORT,
|
||||
} from '../../constants';
|
||||
import { NAME, LAST_SEEN, CREATED, EXPIRED, STATUS } from '../../constants';
|
||||
|
||||
import styles from '../ReportToggleList.module.scss';
|
||||
import { useStyles } from '../ReportToggleList.styles';
|
||||
|
||||
const ReportToggleListHeader = ({
|
||||
handleCheckAll,
|
||||
checkAll,
|
||||
setSortData,
|
||||
setSort,
|
||||
bulkActionsOn,
|
||||
}) => {
|
||||
const styles = useStyles();
|
||||
const handleSort = type => {
|
||||
setSortData(prev => ({
|
||||
sortKey: type,
|
||||
ascending: !prev.ascending,
|
||||
setSort(prev => ({
|
||||
type,
|
||||
desc: !prev.desc,
|
||||
}));
|
||||
};
|
||||
|
||||
@ -94,7 +88,7 @@ const ReportToggleListHeader = ({
|
||||
<th
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => handleSort(REPORT)}
|
||||
onClick={() => handleSort(EXPIRED)}
|
||||
>
|
||||
Report
|
||||
<UnfoldMoreOutlinedIcon className={styles.sortIcon} />
|
||||
@ -106,7 +100,7 @@ const ReportToggleListHeader = ({
|
||||
|
||||
ReportToggleListHeader.propTypes = {
|
||||
checkAll: PropTypes.bool.isRequired,
|
||||
setSortData: PropTypes.func.isRequired,
|
||||
setSort: PropTypes.func.isRequired,
|
||||
bulkActionsOn: PropTypes.bool.isRequired,
|
||||
handleCheckAll: PropTypes.func.isRequired,
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Checkbox } from '@material-ui/core';
|
||||
import CheckIcon from '@material-ui/icons/Check';
|
||||
@ -21,7 +21,7 @@ import {
|
||||
PERMISSION,
|
||||
} from '../../../../constants/featureToggleTypes';
|
||||
|
||||
import styles from '../ReportToggleList.module.scss';
|
||||
import { useStyles } from '../ReportToggleList.styles';
|
||||
import { getTogglePath } from '../../../../utils/routePathHelpers';
|
||||
|
||||
const ReportToggleListItem = ({
|
||||
@ -35,8 +35,8 @@ const ReportToggleListItem = ({
|
||||
bulkActionsOn,
|
||||
setFeatures,
|
||||
}) => {
|
||||
const styles = useStyles();
|
||||
const nameMatches = feature => feature.name === name;
|
||||
const history = useHistory();
|
||||
|
||||
const handleChange = () => {
|
||||
setFeatures(prevState => {
|
||||
@ -116,21 +116,12 @@ const ReportToggleListItem = ({
|
||||
);
|
||||
};
|
||||
|
||||
const navigateToFeature = () => {
|
||||
history.push(getTogglePath(project, name));
|
||||
};
|
||||
|
||||
const statusClasses = classnames(styles.active, styles.hideColumnStatus, {
|
||||
[styles.stale]: stale,
|
||||
});
|
||||
|
||||
return (
|
||||
<tr
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={navigateToFeature}
|
||||
className={styles.tableRow}
|
||||
>
|
||||
<tr className={styles.tableRow}>
|
||||
<ConditionallyRender
|
||||
condition={bulkActionsOn}
|
||||
show={
|
||||
@ -144,7 +135,11 @@ const ReportToggleListItem = ({
|
||||
</td>
|
||||
}
|
||||
/>
|
||||
<td>{name}</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}`}>
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* SORT TYPES */
|
||||
export const NAME = 'name';
|
||||
export const LAST_SEEN = 'lastSeen';
|
||||
export const LAST_SEEN = 'last-seen';
|
||||
export const CREATED = 'created';
|
||||
export const EXPIRED = 'expired';
|
||||
export const STATUS = 'status';
|
||||
|
@ -16,5 +16,6 @@ export const useStyles = makeStyles(theme => ({
|
||||
'& > *': {
|
||||
verticalAlign: 'middle',
|
||||
},
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
}));
|
||||
|
@ -2,7 +2,6 @@ import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
export const useStyles = makeStyles(theme => ({
|
||||
tableRow: {
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.grey[200],
|
||||
},
|
||||
@ -46,6 +45,7 @@ export const useStyles = makeStyles(theme => ({
|
||||
},
|
||||
link: {
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
color: theme.palette.primary.main,
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
},
|
||||
}));
|
||||
|
@ -9,15 +9,15 @@ import {
|
||||
import classnames from 'classnames';
|
||||
import { useStyles } from './FeatureToggleListNew.styles';
|
||||
import FeatureToggleListNewItem from './FeatureToggleListNewItem/FeatureToggleListNewItem';
|
||||
import usePagination from '../../../hooks/usePagination';
|
||||
|
||||
import usePagination from 'hooks/usePagination';
|
||||
import loadingFeatures from './FeatureToggleListNewItem/loadingFeatures';
|
||||
import {
|
||||
IFeatureToggle,
|
||||
IFeatureToggleListItem,
|
||||
} from '../../../interfaces/featureToggle';
|
||||
import PaginateUI from '../../common/PaginateUI/PaginateUI';
|
||||
import StringTruncator from '../../common/StringTruncator/StringTruncator';
|
||||
} from 'interfaces/featureToggle';
|
||||
import PaginateUI from 'component/common/PaginateUI/PaginateUI';
|
||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||
import { createGlobalStateHook } from 'hooks/useGlobalState';
|
||||
interface IFeatureToggleListNewProps {
|
||||
features: IFeatureToggleListItem[];
|
||||
loading: boolean;
|
||||
@ -66,17 +66,24 @@ const sortList = (list, sortOpt) => {
|
||||
return list;
|
||||
};
|
||||
|
||||
interface ISortedState {
|
||||
field: string;
|
||||
type: string;
|
||||
direction: number;
|
||||
}
|
||||
|
||||
const useFeatureToggLeProjectSort = createGlobalStateHook<ISortedState>(
|
||||
'useFeatureToggLeProjectSort',
|
||||
{ field: 'name', type: 'string', direction: 0 }
|
||||
);
|
||||
|
||||
const FeatureToggleListNew = ({
|
||||
features,
|
||||
loading,
|
||||
projectId,
|
||||
}: IFeatureToggleListNewProps) => {
|
||||
const styles = useStyles();
|
||||
const [sortOpt, setSortOpt] = useState({
|
||||
field: 'name',
|
||||
type: 'string',
|
||||
direction: 0,
|
||||
});
|
||||
const [sortOpt, setSortOpt] = useFeatureToggLeProjectSort();
|
||||
const [sortedFeatures, setSortedFeatures] = useState(
|
||||
sortList([...features], sortOpt)
|
||||
);
|
||||
|
@ -1,12 +1,10 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { TableCell, TableRow } from '@material-ui/core';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useStyles } from '../FeatureToggleListNew.styles';
|
||||
import useToggleFeatureByEnv from '../../../../hooks/api/actions/useToggleFeatureByEnv/useToggleFeatureByEnv';
|
||||
import { IEnvironments } from '../../../../interfaces/featureToggle';
|
||||
import useToast from '../../../../hooks/useToast';
|
||||
import { getTogglePath } from 'utils/routePathHelpers';
|
||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import FeatureStatus from '../../FeatureView/FeatureStatus/FeatureStatus';
|
||||
import FeatureType from '../../FeatureView/FeatureType/FeatureType';
|
||||
import classNames from 'classnames';
|
||||
@ -41,10 +39,8 @@ const FeatureToggleListNewItem = ({
|
||||
name
|
||||
);
|
||||
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { refetch } = useProject(projectId);
|
||||
const styles = useStyles();
|
||||
const history = useHistory();
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
const [showInfoBox, setShowInfoBox] = useState(false);
|
||||
const [environmentName, setEnvironmentName] = useState('');
|
||||
@ -53,12 +49,6 @@ const FeatureToggleListNewItem = ({
|
||||
setShowInfoBox(false);
|
||||
};
|
||||
|
||||
const onClick = (e: React.MouseEvent) => {
|
||||
if (!ref.current?.contains(e.target as Node)) {
|
||||
history.push(getTogglePath(projectId, name));
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggle = (env: IEnvironments) => {
|
||||
toggleFeatureByEnvironment(env.name, env.enabled)
|
||||
.then(() => {
|
||||
@ -87,7 +77,6 @@ const FeatureToggleListNewItem = ({
|
||||
styles.tableCellStatus
|
||||
)}
|
||||
align="left"
|
||||
onClick={onClick}
|
||||
>
|
||||
<FeatureStatus
|
||||
lastSeenAt={lastSeenAt}
|
||||
@ -100,7 +89,6 @@ const FeatureToggleListNewItem = ({
|
||||
styles.tableCellType
|
||||
)}
|
||||
align="center"
|
||||
onClick={onClick}
|
||||
>
|
||||
<FeatureType type={type} />
|
||||
</TableCell>
|
||||
@ -110,11 +98,9 @@ const FeatureToggleListNewItem = ({
|
||||
styles.tableCellName
|
||||
)}
|
||||
align="left"
|
||||
onClick={onClick}
|
||||
>
|
||||
<Link
|
||||
// @ts-expect-error
|
||||
to={getTogglePath(projectId, name, uiConfig.flags.E)}
|
||||
to={getTogglePath(projectId, name)}
|
||||
className={styles.link}
|
||||
>
|
||||
<span data-loading>{name}</span>
|
||||
@ -126,7 +112,6 @@ const FeatureToggleListNewItem = ({
|
||||
styles.tableCellCreated
|
||||
)}
|
||||
align="left"
|
||||
onClick={onClick}
|
||||
>
|
||||
<CreatedAt time={createdAt} />
|
||||
</TableCell>
|
||||
|
@ -40,6 +40,7 @@ export const useStyles = makeStyles(theme => ({
|
||||
},
|
||||
link: {
|
||||
textDecoration: 'none',
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
actionsContainer: {
|
||||
display: 'flex',
|
||||
|
@ -2,18 +2,27 @@ import { IFeatureToggle } from '../interfaces/featureToggle';
|
||||
import React, { useMemo } from 'react';
|
||||
import { getBasePath } from 'utils/formatPath';
|
||||
import { createPersistentGlobalStateHook } from './usePersistentGlobalState';
|
||||
import { parseISO } from 'date-fns';
|
||||
import {
|
||||
expired,
|
||||
getDiffInDays,
|
||||
toggleExpiryByTypeMap,
|
||||
} from 'component/Reporting/utils';
|
||||
|
||||
type FeaturesSortType =
|
||||
| 'name'
|
||||
| 'expired'
|
||||
| 'type'
|
||||
| 'enabled'
|
||||
| 'stale'
|
||||
| 'created'
|
||||
| 'last-seen'
|
||||
| 'status'
|
||||
| 'project';
|
||||
|
||||
interface IFeaturesSort {
|
||||
type: FeaturesSortType;
|
||||
desc?: boolean;
|
||||
}
|
||||
|
||||
export interface IFeaturesSortOutput {
|
||||
@ -63,7 +72,7 @@ export const createFeaturesFilterSortOptions =
|
||||
];
|
||||
};
|
||||
|
||||
const sortFeatures = (
|
||||
const sortAscendingFeatures = (
|
||||
features: IFeatureToggle[],
|
||||
sort: IFeaturesSort
|
||||
): IFeatureToggle[] => {
|
||||
@ -82,12 +91,29 @@ const sortFeatures = (
|
||||
return sortByProject(features);
|
||||
case 'type':
|
||||
return sortByType(features);
|
||||
case 'expired':
|
||||
return sortByExpired(features);
|
||||
case 'status':
|
||||
return sortByStatus(features);
|
||||
default:
|
||||
console.error(`Unknown feature sort type: ${sort.type}`);
|
||||
return features;
|
||||
}
|
||||
};
|
||||
|
||||
const sortFeatures = (
|
||||
features: IFeatureToggle[],
|
||||
sort: IFeaturesSort
|
||||
): IFeatureToggle[] => {
|
||||
const sorted = sortAscendingFeatures(features, sort);
|
||||
|
||||
if (sort.desc) {
|
||||
return [...sorted].reverse();
|
||||
}
|
||||
|
||||
return sorted;
|
||||
};
|
||||
|
||||
const sortByEnabled = (
|
||||
features: Readonly<IFeatureToggle[]>
|
||||
): IFeatureToggle[] => {
|
||||
@ -137,3 +163,43 @@ const sortByProject = (
|
||||
const sortByType = (features: Readonly<IFeatureToggle[]>): IFeatureToggle[] => {
|
||||
return [...features].sort((a, b) => a.type.localeCompare(b.type));
|
||||
};
|
||||
const sortByExpired = (
|
||||
features: Readonly<IFeatureToggle[]>
|
||||
): IFeatureToggle[] => {
|
||||
return [...features].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 expiration = toggleExpiryByTypeMap as Record<string, number>;
|
||||
const expiredByA = diffA - expiration[a.type];
|
||||
const expiredByB = diffB - expiration[b.type];
|
||||
|
||||
return expiredByB - expiredByA;
|
||||
});
|
||||
};
|
||||
|
||||
const sortByStatus = (
|
||||
features: Readonly<IFeatureToggle[]>
|
||||
): IFeatureToggle[] => {
|
||||
return [...features].sort((a, b) => {
|
||||
if (a.stale) {
|
||||
return 1;
|
||||
} else if (b.stale) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user