1
0
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:
Youssef Khedher 2022-03-25 15:30:52 +01:00 committed by GitHub
parent cfc2338e78
commit 4589a19e03
12 changed files with 218 additions and 166 deletions

View File

@ -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}
/>

View File

@ -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;
}
}

View File

@ -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',
},
},
}));

View File

@ -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,
};

View File

@ -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}`}>

View File

@ -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';

View File

@ -16,5 +16,6 @@ export const useStyles = makeStyles(theme => ({
'& > *': {
verticalAlign: 'middle',
},
color: theme.palette.primary.main,
},
}));

View File

@ -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,
},
}));

View File

@ -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)
);

View File

@ -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>

View File

@ -40,6 +40,7 @@ export const useStyles = makeStyles(theme => ({
},
link: {
textDecoration: 'none',
color: theme.palette.primary.main,
},
actionsContainer: {
display: 'flex',

View File

@ -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;
}
});
};