1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01:00

feat: update health report (#541)

* fix: align styling with other pages

* feat: add last updat to heath report

* add feature status to last seen in health overview

* fix: add mobile style

* fix:add background color on table hover

Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com>
This commit is contained in:
Youssef Khedher 2021-12-07 13:17:32 +01:00 committed by GitHub
parent bb38df5293
commit 31d88da08b
10 changed files with 196 additions and 52 deletions

View File

@ -1,4 +1,3 @@
import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { Paper } from '@material-ui/core'; import { Paper } from '@material-ui/core';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -8,12 +7,14 @@ import ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined'
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
import styles from './ReportCard.module.scss'; import styles from './ReportCard.module.scss';
import ReactTimeAgo from 'react-timeago';
const ReportCard = ({ const ReportCard = ({
health, health,
activeCount, activeCount,
staleCount, staleCount,
potentiallyStaleCount, potentiallyStaleCount,
lastUpdate,
}) => { }) => {
const healthLessThan50 = health < 50; const healthLessThan50 = health < 50;
const healthLessThan75 = health < 75; const healthLessThan75 = health < 75;
@ -52,11 +53,22 @@ const ReportCard = ({
<div className={styles.reportCardHealthInnerContainer}> <div className={styles.reportCardHealthInnerContainer}>
<ConditionallyRender <ConditionallyRender
condition={health > -1} condition={health > -1}
show={<p className={healthClasses}>{health}%</p>} show={
<div>
<p className={healthClasses}>{health}%</p>
<p className={styles.lastUpdate}>
Last updated:{' '}
<ReactTimeAgo
date={lastUpdate}
live={false}
/>
</p>
</div>
}
/> />
</div> </div>
</div> </div>
<div className={styles.reportCardListContainer}> <div className={styles.reportCardToggle}>
<h2 className={styles.header}>Toggle report</h2> <h2 className={styles.header}>Toggle report</h2>
<ul className={styles.reportCardList}> <ul className={styles.reportCardList}>
<li> <li>
@ -65,28 +77,49 @@ const ReportCard = ({
show={renderActiveToggles} show={renderActiveToggles}
/> />
</li> </li>
<ConditionallyRender
condition={activeCount}
show={
<p className={styles.reportCardActionText}>
Also includes potentially stale toggles.
</p>
}
/>
<li> <li>
<ConditionallyRender <ConditionallyRender
condition={staleCount} condition={staleCount}
show={renderStaleToggles} show={renderStaleToggles}
/> />
</li> </li>
<li>
<ConditionallyRender
condition={potentiallyStaleCount}
show={renderPotentiallyStaleToggles}
/>
</li>
</ul> </ul>
</div> </div>
<div className={styles.reportCardAction}> <div className={styles.reportCardAction}>
<h2 className={styles.header}>Potential actions</h2> <h2 className={styles.header}>Potential actions</h2>
<div className={styles.reportCardActionContainer}> <div className={styles.reportCardActionContainer}>
<ul className={styles.reportCardList}>
<li>
<ConditionallyRender
condition={potentiallyStaleCount}
show={renderPotentiallyStaleToggles}
/>
</li>
</ul>
<ConditionallyRender
condition={potentiallyStaleCount}
show={
<p className={styles.reportCardActionText}> <p className={styles.reportCardActionText}>
Review your feature toggles and delete unused Review your feature toggles and delete
toggles. unused toggles.
</p> </p>
}
elseShow={
<p className={styles.reportCardNoActionText}>
No action is required
</p>
}
/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,6 +2,8 @@
width: 100%; width: 100%;
padding: var(--card-padding); padding: var(--card-padding);
margin: var(--card-margin-y) 0; margin: var(--card-margin-y) 0;
border-radius: 10px;
box-shadow: none;
} }
.header { .header {
@ -14,9 +16,19 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.reportCardHealth {
padding: 10px;
}
.reportCardAction {
padding: 10px;
}
.reportCardToggle {
padding: 10px;
}
.reportCardHealthInnerContainer { .reportCardHealthInnerContainer {
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -70,6 +82,12 @@
.reportCardActionText { .reportCardActionText {
max-width: 300px; max-width: 300px;
font-size: var(--p-size); font-size: var(--p-size);
margin-left: 35px;
}
.reportCardNoActionText {
max-width: 300px;
font-size: var(--p-size);
margin-left: 15px;
} }
.reportCardBtn { .reportCardBtn {
@ -83,3 +101,16 @@
.healthWarning { .healthWarning {
color: var(--warning); color: var(--warning);
} }
.lastUpdate {
color: #585858;
}
@media (max-width: 600px) {
.reportCardContainer {
flex-wrap: wrap;
}
.reportCardToggle {
margin: 10px 5px;
flex-basis: 100%;
}
}

View File

@ -1,6 +1,8 @@
.reportToggleList { .reportToggleList {
width: 100%; width: 100%;
margin: var(--card-margin-y) 0; margin: var(--card-margin-y) 0;
border-radius: 10px;
box-shadow: none;
} }
.bulkAction { .bulkAction {
@ -65,7 +67,31 @@
cursor: pointer; cursor: pointer;
} }
.tableRow:hover {
background-color: #eeeeee;
}
.checkbox { .checkbox {
margin: 0; margin: 0;
padding: 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

@ -5,11 +5,23 @@ import PropTypes from 'prop-types';
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender'; import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
import { NAME, LAST_SEEN, CREATED, EXPIRED, STATUS, REPORT } from '../../constants'; import {
NAME,
LAST_SEEN,
CREATED,
EXPIRED,
STATUS,
REPORT,
} from '../../constants';
import styles from '../ReportToggleList.module.scss'; import styles from '../ReportToggleList.module.scss';
const ReportToggleListHeader = ({ handleCheckAll, checkAll, setSortData, bulkActionsOn }) => { const ReportToggleListHeader = ({
handleCheckAll,
checkAll,
setSortData,
bulkActionsOn,
}) => {
const handleSort = type => { const handleSort = type => {
setSortData(prev => ({ setSortData(prev => ({
sortKey: type, sortKey: type,
@ -34,27 +46,56 @@ const ReportToggleListHeader = ({ handleCheckAll, checkAll, setSortData, bulkAct
} }
/> />
<th role="button" tabIndex={0} style={{ width: '150px' }} onClick={() => handleSort(NAME)}> <th
role="button"
tabIndex={0}
style={{ width: '150px' }}
onClick={() => handleSort(NAME)}
>
Name Name
<UnfoldMoreOutlinedIcon className={styles.sortIcon} /> <UnfoldMoreOutlinedIcon className={styles.sortIcon} />
</th> </th>
<th role="button" tabIndex={0} onClick={() => handleSort(LAST_SEEN)}> <th
role="button"
className={styles.hideColumnLastSeen}
tabIndex={0}
onClick={() => handleSort(LAST_SEEN)}
>
Last seen Last seen
<UnfoldMoreOutlinedIcon className={styles.sortIcon} /> <UnfoldMoreOutlinedIcon className={styles.sortIcon} />
</th> </th>
<th role="button" tabIndex={0} onClick={() => handleSort(CREATED)}> <th
role="button"
tabIndex={0}
className={styles.hideColumn}
onClick={() => handleSort(CREATED)}
>
Created Created
<UnfoldMoreOutlinedIcon className={styles.sortIcon} /> <UnfoldMoreOutlinedIcon className={styles.sortIcon} />
</th> </th>
<th role="button" tabIndex={0} onClick={() => handleSort(EXPIRED)}> <th
role="button"
tabIndex={0}
className={styles.hideColumn}
onClick={() => handleSort(EXPIRED)}
>
Expired Expired
<UnfoldMoreOutlinedIcon className={styles.sortIcon} /> <UnfoldMoreOutlinedIcon className={styles.sortIcon} />
</th> </th>
<th role="button" tabIndex={0} onClick={() => handleSort(STATUS)}> <th
role="button"
tabIndex={0}
className={styles.hideColumnStatus}
onClick={() => handleSort(STATUS)}
>
Status Status
<UnfoldMoreOutlinedIcon className={styles.sortIcon} /> <UnfoldMoreOutlinedIcon className={styles.sortIcon} />
</th> </th>
<th role="button" tabIndex={0} onClick={() => handleSort(REPORT)}> <th
role="button"
tabIndex={0}
onClick={() => handleSort(REPORT)}
>
Report Report
<UnfoldMoreOutlinedIcon className={styles.sortIcon} /> <UnfoldMoreOutlinedIcon className={styles.sortIcon} />
</th> </th>

View File

@ -7,6 +7,7 @@ import { Checkbox } from '@material-ui/core';
import CheckIcon from '@material-ui/icons/Check'; import CheckIcon from '@material-ui/icons/Check';
import ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined'; import ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined';
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender'; import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
import FeatureStatus from '../../../feature/FeatureView2/FeatureStatus/FeatureStatus';
import { import {
pluralize, pluralize,
@ -75,20 +76,13 @@ const ReportToggleListItem = ({
return pluralize(result, 'day'); return pluralize(result, 'day');
} }
return 'N/A';
}; };
const formatLastSeenAt = () => { const formatLastSeenAt = () => {
if (!lastSeenAt) return 'Never'; return (
<FeatureStatus lastSeenAt={lastSeenAt} tooltipPlacement="bottom" />
const [date, now] = getDates(lastSeenAt); );
const diff = getDiffInDays(date, now);
if (diff === 0) return '1 day';
if (diff) {
return pluralize(diff, 'day');
}
return '1 day';
}; };
const renderStatus = (icon, text) => ( const renderStatus = (icon, text) => (
@ -126,7 +120,7 @@ const ReportToggleListItem = ({
history.push(getTogglePath(project, name)); history.push(getTogglePath(project, name));
}; };
const statusClasses = classnames(styles.active, { const statusClasses = classnames(styles.active, styles.hideColumnStatus, {
[styles.stale]: stale, [styles.stale]: stale,
}); });
@ -151,9 +145,11 @@ const ReportToggleListItem = ({
} }
/> />
<td>{name}</td> <td>{name}</td>
<td>{formatLastSeenAt()}</td> <td className={styles.hideColumnLastSeen}>{formatLastSeenAt()}</td>
<td>{formatCreatedAt()}</td> <td className={styles.hideColumn}>{formatCreatedAt()}</td>
<td className={styles.expired}>{formatExpiredAt()}</td> <td className={`${styles.expired} ${styles.hideColumn}`}>
{formatExpiredAt()}
</td>
<td className={statusClasses}>{stale ? 'Stale' : 'Active'}</td> <td className={statusClasses}>{stale ? 'Stale' : 'Active'}</td>
<td>{formatReportStatus()}</td> <td>{formatReportStatus()}</td>
</tr> </tr>

View File

@ -58,7 +58,10 @@ const FeatureToggleListItem = ({
className={classnames(styles.listItem, rest.className)} className={classnames(styles.listItem, rest.className)}
> >
<span className={styles.listItemMetric}> <span className={styles.listItemMetric}>
<FeatureStatus lastSeenAt={lastSeenAt} /> <FeatureStatus
lastSeenAt={lastSeenAt}
tooltipPlacement="left"
/>
</span> </span>
<span <span
className={classnames( className={classnames(

View File

@ -83,7 +83,10 @@ const FeatureToggleListNewItem = ({
align="left" align="left"
onClick={onClick} onClick={onClick}
> >
<FeatureStatus lastSeenAt={lastSeenAt} /> <FeatureStatus
lastSeenAt={lastSeenAt}
tooltipPlacement="left"
/>
</TableCell> </TableCell>
<TableCell <TableCell
className={classNames( className={classNames(

View File

@ -47,16 +47,20 @@ function getColor(unit?: string): string {
interface FeatureStatusProps { interface FeatureStatusProps {
lastSeenAt?: Date; lastSeenAt?: Date;
tooltipPlacement?: string;
} }
const FeatureStatus = ({ lastSeenAt }: FeatureStatusProps) => { const FeatureStatus = ({
lastSeenAt,
tooltipPlacement,
}: FeatureStatusProps) => {
const styles = useStyles(); const styles = useStyles();
const Wrapper = ( const Wrapper = (
props: React.PropsWithChildren<{ color: string; toolTip: string }> props: React.PropsWithChildren<{ color: string; toolTip: string }>
) => { ) => {
return ( return (
<Tooltip title={props.toolTip} arrow placement="left"> <Tooltip title={props.toolTip} arrow placement={tooltipPlacement}>
<div <div
className={styles.container} className={styles.container}
style={{ background: props.color, fontSize: '0.8rem' }} style={{ background: props.color, fontSize: '0.8rem' }}
@ -83,7 +87,9 @@ const FeatureStatus = ({ lastSeenAt }: FeatureStatusProps) => {
) => { ) => {
return ( return (
<Wrapper <Wrapper
toolTip={`Last usage reported ${value} ${unit}${value !== 1 ? 's' : ''} ${suffix}`} toolTip={`Last usage reported ${value} ${unit}${
value !== 1 ? 's' : ''
} ${suffix}`}
color={getColor(unit)} color={getColor(unit)}
> >
{value} {value}
@ -94,7 +100,10 @@ const FeatureStatus = ({ lastSeenAt }: FeatureStatusProps) => {
/> />
} }
elseShow={ elseShow={
<Wrapper toolTip="No usage reported from connected applications" color={getColor()}> <Wrapper
toolTip="No usage reported from connected applications"
color={getColor()}
>
<span style={{ fontSize: '1.4rem' }}></span> <span style={{ fontSize: '1.4rem' }}></span>
</Wrapper> </Wrapper>
} }

View File

@ -1,8 +1,8 @@
import useHealthReport from '../../../../hooks/api/getters/useHealthReport/useHealthReport'; import useHealthReport from '../../../../hooks/api/getters/useHealthReport/useHealthReport';
import ApiError from '../../../common/ApiError/ApiError'; import ApiError from '../../../common/ApiError/ApiError';
import ConditionallyRender from '../../../common/ConditionallyRender'; import ConditionallyRender from '../../../common/ConditionallyRender';
import ReportCardContainer from '../../../Reporting/ReportCard/ReportCardContainer' import ReportCardContainer from '../../../Reporting/ReportCard/ReportCardContainer';
import ReportToggleList from '../../../Reporting/ReportToggleList/ReportToggleList' import ReportToggleList from '../../../Reporting/ReportToggleList/ReportToggleList';
interface ProjectHealthProps { interface ProjectHealthProps {
projectId: string; projectId: string;
@ -30,13 +30,14 @@ const ProjectHealth = ({ projectId }: ProjectHealthProps) => {
activeCount={project?.activeCount} activeCount={project?.activeCount}
potentiallyStaleCount={project?.potentiallyStaleCount} potentiallyStaleCount={project?.potentiallyStaleCount}
selectedProject={project.name} selectedProject={project.name}
lastUpdate={project.updatedAt}
/> />
<ReportToggleList <ReportToggleList
features={project.features} features={project.features}
selectedProject={projectId} selectedProject={projectId}
/> />
</div> </div>
) );
} };
export default ProjectHealth; export default ProjectHealth;

View File

@ -24,4 +24,5 @@ export interface IProjectHealthReport extends IProject {
staleCount: number; staleCount: number;
potentiallyStaleCount: number; potentiallyStaleCount: number;
activeCount: number; activeCount: number;
updatedAt: Date;
} }