diff --git a/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.jsx b/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.jsx index 8ba05c14ee..7f5ae57d84 100644 --- a/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.jsx +++ b/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.jsx @@ -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 => ( { diff --git a/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.module.scss b/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.module.scss deleted file mode 100644 index 1400b00ae9..0000000000 --- a/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.module.scss +++ /dev/null @@ -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; - } -} diff --git a/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.styles.ts b/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.styles.ts new file mode 100644 index 0000000000..2707be78ab --- /dev/null +++ b/frontend/src/component/Reporting/ReportToggleList/ReportToggleList.styles.ts @@ -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', + }, + }, +})); diff --git a/frontend/src/component/Reporting/ReportToggleList/ReportToggleListHeader/ReportToggleListHeader.jsx b/frontend/src/component/Reporting/ReportToggleList/ReportToggleListHeader/ReportToggleListHeader.jsx index 016bd26642..805287d97b 100644 --- a/frontend/src/component/Reporting/ReportToggleList/ReportToggleListHeader/ReportToggleListHeader.jsx +++ b/frontend/src/component/Reporting/ReportToggleList/ReportToggleListHeader/ReportToggleListHeader.jsx @@ -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 = ({ handleSort(REPORT)} + onClick={() => handleSort(EXPIRED)} > Report @@ -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, }; diff --git a/frontend/src/component/Reporting/ReportToggleList/ReportToggleListItem/ReportToggleListItem.jsx b/frontend/src/component/Reporting/ReportToggleList/ReportToggleListItem/ReportToggleListItem.jsx index 9737fe163c..8f8c0a57c0 100644 --- a/frontend/src/component/Reporting/ReportToggleList/ReportToggleListItem/ReportToggleListItem.jsx +++ b/frontend/src/component/Reporting/ReportToggleList/ReportToggleListItem/ReportToggleListItem.jsx @@ -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 ( - + } /> - {name} + + + {name} + + {formatLastSeenAt()} {formatCreatedAt()} diff --git a/frontend/src/component/Reporting/constants.js b/frontend/src/component/Reporting/constants.js index 4feb40d730..043590e411 100644 --- a/frontend/src/component/Reporting/constants.js +++ b/frontend/src/component/Reporting/constants.js @@ -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'; diff --git a/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.styles.ts b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.styles.ts index d1dfa48d38..e85e409925 100644 --- a/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.styles.ts +++ b/frontend/src/component/common/BreadcrumbNav/BreadcrumbNav.styles.ts @@ -16,5 +16,6 @@ export const useStyles = makeStyles(theme => ({ '& > *': { verticalAlign: 'middle', }, + color: theme.palette.primary.main, }, })); diff --git a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.styles.ts b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.styles.ts index 9834c942ab..df607bec8e 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.styles.ts +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.styles.ts @@ -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, }, })); diff --git a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx index 5da617a94d..81664e0faf 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNew.tsx @@ -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( + '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) ); diff --git a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx index 3d7402b612..2fe566b804 100644 --- a/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx +++ b/frontend/src/component/feature/FeatureToggleListNew/FeatureToggleListNewItem/FeatureToggleListNewItem.tsx @@ -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(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} > @@ -110,11 +98,9 @@ const FeatureToggleListNewItem = ({ styles.tableCellName )} align="left" - onClick={onClick} > {name} @@ -126,7 +112,6 @@ const FeatureToggleListNewItem = ({ styles.tableCellCreated )} align="left" - onClick={onClick} > diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.styles.ts b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.styles.ts index 01c4c66fca..76b6c30ac7 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.styles.ts +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.styles.ts @@ -40,6 +40,7 @@ export const useStyles = makeStyles(theme => ({ }, link: { textDecoration: 'none', + color: theme.palette.primary.main, }, actionsContainer: { display: 'flex', diff --git a/frontend/src/hooks/useFeaturesSort.ts b/frontend/src/hooks/useFeaturesSort.ts index bd4a40ef93..89f7c0c843 100644 --- a/frontend/src/hooks/useFeaturesSort.ts +++ b/frontend/src/hooks/useFeaturesSort.ts @@ -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[] => { @@ -137,3 +163,43 @@ const sortByProject = ( const sortByType = (features: Readonly): IFeatureToggle[] => { return [...features].sort((a, b) => a.type.localeCompare(b.type)); }; +const sortByExpired = ( + features: Readonly +): 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; + const expiredByA = diffA - expiration[a.type]; + const expiredByB = diffB - expiration[b.type]; + + return expiredByB - expiredByA; + }); +}; + +const sortByStatus = ( + features: Readonly +): IFeatureToggle[] => { + return [...features].sort((a, b) => { + if (a.stale) { + return 1; + } else if (b.stale) { + return -1; + } else { + return 0; + } + }); +};