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

Merge branch 'master' into feat/API-token-improvement

This commit is contained in:
Youssef Khedher 2021-10-08 14:51:22 +01:00 committed by GitHub
commit 338c74e955
15 changed files with 246 additions and 168 deletions

View File

@ -49,6 +49,7 @@
"@types/react": "17.0.27", "@types/react": "17.0.27",
"@types/react-dom": "17.0.9", "@types/react-dom": "17.0.9",
"@types/react-router-dom": "5.3.1", "@types/react-router-dom": "5.3.1",
"@types/react-timeago": "^4.1.3",
"@welldone-software/why-did-you-render": "6.2.1", "@welldone-software/why-did-you-render": "6.2.1",
"array-move": "3.0.1", "array-move": "3.0.1",
"classnames": "2.3.1", "classnames": "2.3.1",

View File

@ -3,20 +3,22 @@ import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Switch, IconButton, ListItem } from '@material-ui/core'; import { IconButton, ListItem } from '@material-ui/core';
import { Undo } from '@material-ui/icons'; import { Undo } from '@material-ui/icons';
import TimeAgo from 'react-timeago'; import TimeAgo from 'react-timeago';
import Progress from '../../ProgressWheel';
import Status from '../../status-component'; import Status from '../../status-component';
import FeatureToggleListItemChip from './FeatureToggleListItemChip'; import FeatureToggleListItemChip from './FeatureToggleListItemChip';
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender'; import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
import { UPDATE_FEATURE } from '../../../AccessProvider/permissions'; import { UPDATE_FEATURE } from '../../../AccessProvider/permissions';
import { calc, styles as commonStyles } from '../../../common'; import { styles as commonStyles } from '../../../common';
import { useStyles } from './styles'; import { useStyles } from './styles';
import { getTogglePath } from '../../../../utils/route-path-helpers'; import { getTogglePath } from '../../../../utils/route-path-helpers';
import FeatureStatus from '../../FeatureView2/FeatureStatus/FeatureStatus';
const FeatureToggleListItem = ({ const FeatureToggleListItem = ({
feature, feature,
@ -30,25 +32,9 @@ const FeatureToggleListItem = ({
}) => { }) => {
const styles = useStyles(); const styles = useStyles();
const { name, description, enabled, type, stale, createdAt, project } = const { name, description, type, stale, createdAt, project, lastSeenAt } =
feature; feature;
const { showLastHour = false } = settings;
const isStale = showLastHour
? metricsLastHour.isFallback
: metricsLastMinute.isFallback;
const percent =
1 *
(showLastHour
? calc(
metricsLastHour.yes,
metricsLastHour.yes + metricsLastHour.no,
0
)
: calc(
metricsLastMinute.yes,
metricsLastMinute.yes + metricsLastMinute.no,
0
));
const featureUrl = const featureUrl =
toggleFeature === undefined toggleFeature === undefined
? `/projects/${feature.project}/archived/${name}/metrics` ? `/projects/${feature.project}/archived/${name}/metrics`
@ -60,33 +46,7 @@ const FeatureToggleListItem = ({
className={classnames(styles.listItem, rest.className)} className={classnames(styles.listItem, rest.className)}
> >
<span className={styles.listItemMetric}> <span className={styles.listItemMetric}>
<Progress <FeatureStatus lastSeenAt={lastSeenAt} />
strokeWidth={15}
percentage={percent}
isFallback={isStale}
/>
</span>
<span className={styles.listItemToggle}>
<ConditionallyRender
condition={hasAccess(UPDATE_FEATURE, project)}
show={
<Switch
disabled={toggleFeature === undefined}
title={`Toggle ${name}`}
key="left-actions"
onChange={() => toggleFeature(!enabled, name)}
checked={enabled}
/>
}
elseShow={
<Switch
disabled
title={`Toggle ${name}`}
key="left-actions"
checked={enabled}
/>
}
/>
</span> </span>
<span className={classnames(styles.listItemLink)}> <span className={classnames(styles.listItemLink)}>
<Link <Link

View File

@ -8,55 +8,33 @@ exports[`renders correctly with one feature 1`] = `
<span <span
className="makeStyles-listItemMetric-2" className="makeStyles-listItemMetric-2"
> >
<svg <div
viewBox="0 0 24 24" aria-describedby={null}
> className="makeStyles-container-6"
<path
d="M17.3,18C19,16.5 20,14.4 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12C4,14.4 5,16.5 6.7,18C8.2,16.7 10,16 12,16C14,16 15.9,16.7 17.3,18M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M7,9A1,1 0 0,1 8,10A1,1 0 0,1 7,11A1,1 0 0,1 6,10A1,1 0 0,1 7,9M10,6A1,1 0 0,1 11,7A1,1 0 0,1 10,8A1,1 0 0,1 9,7A1,1 0 0,1 10,6M17,9A1,1 0 0,1 18,10A1,1 0 0,1 17,11A1,1 0 0,1 16,10A1,1 0 0,1 17,9M14.4,6.1C14.9,6.3 15.1,6.9 15,7.4L13.6,10.8C13.8,11.1 14,11.5 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12C10,11 10.7,10.1 11.7,10L13.1,6.7C13.3,6.1 13.9,5.9 14.4,6.1Z"
fill="#E0E0E0"
/>
</svg>
</span>
<span>
<span
className="MuiSwitch-root"
>
<span
aria-disabled={false}
className="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-6 MuiSwitch-switchBase MuiSwitch-colorSecondary"
onBlur={[Function]} onBlur={[Function]}
onDragLeave={[Function]}
onFocus={[Function]} onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseUp={[Function]} onMouseOver={[Function]}
onTouchEnd={[Function]} onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]} onTouchStart={[Function]}
tabIndex={null} style={
title="Toggle Another" Object {
"background": "#EDF0F1",
"fontSize": "0.8rem",
}
}
title="No usage reported"
> >
<span <span
className="MuiIconButton-label" style={
Object {
"fontSize": "1.4rem",
}
}
> >
<input
checked={false}
className="PrivateSwitchBase-input-9 MuiSwitch-input"
disabled={false}
onChange={[Function]}
type="checkbox"
/>
<span
className="MuiSwitch-thumb"
/>
</span>
</span>
<span
className="MuiSwitch-track"
/>
</span> </span>
</div>
</span> </span>
<span <span
className="makeStyles-listItemLink-4" className="makeStyles-listItemLink-4"
@ -105,58 +83,33 @@ exports[`renders correctly with one feature without permission 1`] = `
<span <span
className="makeStyles-listItemMetric-2" className="makeStyles-listItemMetric-2"
> >
<svg <div
viewBox="0 0 24 24" aria-describedby={null}
> className="makeStyles-container-6"
<path
d="M17.3,18C19,16.5 20,14.4 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12C4,14.4 5,16.5 6.7,18C8.2,16.7 10,16 12,16C14,16 15.9,16.7 17.3,18M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M7,9A1,1 0 0,1 8,10A1,1 0 0,1 7,11A1,1 0 0,1 6,10A1,1 0 0,1 7,9M10,6A1,1 0 0,1 11,7A1,1 0 0,1 10,8A1,1 0 0,1 9,7A1,1 0 0,1 10,6M17,9A1,1 0 0,1 18,10A1,1 0 0,1 17,11A1,1 0 0,1 16,10A1,1 0 0,1 17,9M14.4,6.1C14.9,6.3 15.1,6.9 15,7.4L13.6,10.8C13.8,11.1 14,11.5 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12C10,11 10.7,10.1 11.7,10L13.1,6.7C13.3,6.1 13.9,5.9 14.4,6.1Z"
fill="#E0E0E0"
/>
</svg>
</span>
<span>
<span
className="MuiSwitch-root"
>
<span
aria-disabled={false}
className="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-6 MuiSwitch-switchBase MuiSwitch-colorSecondary"
onBlur={[Function]} onBlur={[Function]}
onDragLeave={[Function]}
onFocus={[Function]} onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]} onMouseLeave={[Function]}
onMouseUp={[Function]} onMouseOver={[Function]}
onTouchEnd={[Function]} onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]} onTouchStart={[Function]}
tabIndex={null} style={
title="Toggle Another" Object {
"background": "#EDF0F1",
"fontSize": "0.8rem",
}
}
title="No usage reported"
> >
<span <span
className="MuiIconButton-label" style={
Object {
"fontSize": "1.4rem",
}
}
> >
<input
checked={false}
className="PrivateSwitchBase-input-9 MuiSwitch-input"
disabled={false}
onChange={[Function]}
type="checkbox"
/>
<span
className="MuiSwitch-thumb"
/>
</span>
<span
className="MuiTouchRipple-root"
/>
</span>
<span
className="MuiSwitch-track"
/>
</span> </span>
</div>
</span> </span>
<span <span
className="makeStyles-listItemLink-4" className="makeStyles-listItemLink-4"

View File

@ -19,20 +19,24 @@ export const useStyles = makeStyles(theme => ({
display: 'none', display: 'none',
}, },
}, },
tableCellStatus: {
width: '50px',
},
tableCellName: { tableCellName: {
width: '250px', width: '250px',
display: 'flex',
}, },
tableCellEnv: { tableCellEnv: {
width: '20px', width: '20px',
}, },
tableCellType: { tableCellType: {
display: 'flex', width: '32px',
alignItems: 'center', alignItems: 'center',
[theme.breakpoints.down('sm')]: { [theme.breakpoints.down('sm')]: {
display: 'none', display: 'none',
}, },
}, },
icon: { icon: {
marginRight: '0.3rem', color: theme.palette.grey[600],
}, },
})); }));

View File

@ -11,7 +11,10 @@ import FeatureToggleListNewItem from './FeatureToggleListNewItem/FeatureToggleLi
import usePagination from '../../../hooks/usePagination'; import usePagination from '../../../hooks/usePagination';
import loadingFeatures from './FeatureToggleListNewItem/loadingFeatures'; import loadingFeatures from './FeatureToggleListNewItem/loadingFeatures';
import { IFeatureToggleListItem } from '../../../interfaces/featureToggle'; import {
IFeatureToggle,
IFeatureToggleListItem,
} from '../../../interfaces/featureToggle';
import PaginateUI from '../../common/PaginateUI/PaginateUI'; import PaginateUI from '../../common/PaginateUI/PaginateUI';
interface IFeatureToggleListNewProps { interface IFeatureToggleListNewProps {
features: IFeatureToggleListItem[]; features: IFeatureToggleListItem[];
@ -26,7 +29,7 @@ const FeatureToggleListNew = ({
}: IFeatureToggleListNewProps) => { }: IFeatureToggleListNewProps) => {
const styles = useStyles(); const styles = useStyles();
const { page, pages, nextPage, prevPage, setPageIndex, pageIndex } = const { page, pages, nextPage, prevPage, setPageIndex, pageIndex } =
usePagination(features, 9); usePagination(features, 50);
const getEnvironments = () => { const getEnvironments = () => {
if (features.length > 0) { if (features.length > 0) {
@ -57,7 +60,7 @@ const FeatureToggleListNew = ({
}); });
} }
return page.map((feature: IFeatureToggleListItem) => { return page.map((feature: IFeatureToggle) => {
return ( return (
<FeatureToggleListNewItem <FeatureToggleListNewItem
key={feature.name} key={feature.name}
@ -65,6 +68,7 @@ const FeatureToggleListNew = ({
type={feature.type} type={feature.type}
environments={feature.environments} environments={feature.environments}
projectId={projectId} projectId={projectId}
lastSeenAt={feature.lastSeenAt}
/> />
); );
}); });
@ -78,12 +82,12 @@ const FeatureToggleListNew = ({
<TableCell <TableCell
className={classnames( className={classnames(
styles.tableCell, styles.tableCell,
styles.tableCellName, styles.tableCellStatus,
styles.tableCellHeader styles.tableCellHeader
)} )}
align="left" align="left"
> >
<span data-loading>Name</span> <span data-loading>Status</span>
</TableCell> </TableCell>
<TableCell <TableCell
className={classnames( className={classnames(
@ -91,10 +95,20 @@ const FeatureToggleListNew = ({
styles.tableCellHeader, styles.tableCellHeader,
styles.typeHeader styles.typeHeader
)} )}
align="left" align="center"
> >
<span data-loading>Type</span> <span data-loading>Type</span>
</TableCell> </TableCell>
<TableCell
className={classnames(
styles.tableCell,
styles.tableCellName,
styles.tableCellHeader
)}
align="left"
>
<span data-loading>Name</span>
</TableCell>
{getEnvironments().map((env: any) => { {getEnvironments().map((env: any) => {
return ( return (
<TableCell <TableCell

View File

@ -3,6 +3,7 @@ import {
Switch, Switch,
TableCell, TableCell,
TableRow, TableRow,
Tooltip,
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from '@material-ui/core'; } from '@material-ui/core';
@ -16,16 +17,19 @@ import useToast from '../../../../hooks/useToast';
import { getTogglePath } from '../../../../utils/route-path-helpers'; import { getTogglePath } from '../../../../utils/route-path-helpers';
import { SyntheticEvent } from 'react-router/node_modules/@types/react'; import { SyntheticEvent } from 'react-router/node_modules/@types/react';
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
import FeatureStatus from '../../FeatureView2/FeatureStatus/FeatureStatus';
interface IFeatureToggleListNewItemProps { interface IFeatureToggleListNewItemProps {
name: string; name: string;
type: string; type: string;
environments: IEnvironments[]; environments: IFeatureEnvironment[];
projectId: string; projectId: string;
lastSeenAt?: Date;
} }
const FeatureToggleListNewItem = ({ const FeatureToggleListNewItem = ({
name, name,
lastSeenAt,
type, type,
environments, environments,
projectId, projectId,
@ -35,7 +39,7 @@ const FeatureToggleListNewItem = ({
const smallScreen = useMediaQuery(theme.breakpoints.down('sm')); const smallScreen = useMediaQuery(theme.breakpoints.down('sm'));
const { toggleFeatureByEnvironment } = useToggleFeatureByEnv( const { toggleFeatureByEnvironment } = useToggleFeatureByEnv(
projectId, projectId,
name name,
); );
const { uiConfig } = useUiConfig(); const { uiConfig } = useUiConfig();
@ -74,22 +78,25 @@ const FeatureToggleListNewItem = ({
<> <>
<TableRow className={styles.tableRow}> <TableRow className={styles.tableRow}>
<TableCell className={styles.tableCell} align="left" onClick={onClick}> <TableCell className={styles.tableCell} align="left" onClick={onClick}>
<span data-loading>{name}</span> <FeatureStatus lastSeenAt={lastSeenAt} />
</TableCell> </TableCell>
<ConditionallyRender <ConditionallyRender
condition={!smallScreen} condition={!smallScreen}
show={ show={
<TableCell className={styles.tableCell} align="left" onClick={onClick}> <TableCell className={styles.tableCell} align="center" onClick={onClick}>
<div className={styles.tableCellType}> <Tooltip arrow placement="right" title={type}>
<IconComponent <IconComponent
data-loading data-loading
className={styles.icon} className={styles.icon}
/>{' '} />
<span data-loading>{type}</span> </Tooltip>
</div>
</TableCell> </TableCell>
} }
/> />
<TableCell className={styles.tableCell} align="left" onClick={onClick}>
<span data-loading>{name}</span>
</TableCell>
{environments.map((env: IEnvironments) => { {environments.map((env: IEnvironments) => {
return ( return (

View File

@ -0,0 +1,16 @@
import { makeStyles } from '@material-ui/core/styles';
export const useStyles = makeStyles(theme => ({
container: {
width: '42px',
height: '42px',
fontSize: '0.7em',
background: 'gray',
borderRadius: '3px',
textAlign: 'center',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '13px 10px',
},
}));

View File

@ -0,0 +1,105 @@
import { useStyles } from './FeatureStatus.styles';
import TimeAgo from 'react-timeago';
import ConditionallyRender from '../../../common/ConditionallyRender';
import { Tooltip } from '@material-ui/core';
function generateUnit(unit?: string): string {
switch (unit) {
case 'second':
return 's';
case 'minute':
return 'm';
case 'hour':
return 'h';
case 'day':
return 'D';
case 'week':
return 'W';
case 'month':
return 'M';
case 'year':
return 'Y';
default:
return '';
}
}
function getColor(unit?: string): string {
switch (unit) {
case 'second':
return '#98E3AF';
case 'minute':
return '#98E3AF';
case 'hour':
return '#98E3AF';
case 'day':
return '#98E3AF';
case 'week':
return '#ECD875';
case 'month':
return '#F5A69A';
case 'year':
return '#F5A69A';
default:
return '#EDF0F1';
}
}
interface FeatureStatusProps {
lastSeenAt?: Date;
}
const FeatureStatus = ({ lastSeenAt }: FeatureStatusProps) => {
const styles = useStyles();
const Wrapper = (
props: React.PropsWithChildren<{ color: string; toolTip: string }>
) => {
return (
<Tooltip title={props.toolTip} arrow placement="left">
<div
className={styles.container}
style={{ background: props.color, fontSize: '0.8rem' }}
>
{props.children}
</div>
</Tooltip>
);
};
return (
<ConditionallyRender
condition={!!lastSeenAt}
show={
//@ts-ignore
<TimeAgo
date={lastSeenAt}
title=""
live={false}
formatter={(
value: number,
unit: string,
suffix: string
) => {
return (
<Wrapper
toolTip={`Last usage reported ${value} ${unit} ${suffix}`}
color={getColor(unit)}
>
{value}
{generateUnit(unit)}
</Wrapper>
);
}}
/>
}
elseShow={
<Wrapper toolTip="No usage reported" color={getColor()}>
<span style={{ fontSize: '1.4rem' }}></span>
</Wrapper>
}
/>
);
};
export default FeatureStatus;

View File

@ -8,6 +8,7 @@ export const useStyles = makeStyles(theme => ({
flexDirection: 'column', flexDirection: 'column',
}, },
}, },
projectToggles: { width: '100%', minHeight: '100%' },
header: { header: {
backgroundColor: '#fff', backgroundColor: '#fff',
borderRadius: '10px', borderRadius: '10px',

View File

@ -3,9 +3,11 @@ import { makeStyles } from '@material-ui/core/styles';
export const useStyles = makeStyles(theme => ({ export const useStyles = makeStyles(theme => ({
container: { container: {
boxShadow: 'none', boxShadow: 'none',
marginLeft: '2rem', marginLeft: '1rem',
width: '100%', minHeight: '100%',
width: 'calc(100% - 1rem)',
position: 'relative', position: 'relative',
paddingBottom: '4rem',
[theme.breakpoints.down('sm')]: { [theme.breakpoints.down('sm')]: {
marginLeft: '0', marginLeft: '0',
paddingBottom: '4rem', paddingBottom: '4rem',

View File

@ -14,6 +14,11 @@ export const useStyles = makeStyles(theme => ({
marginBottom: '1rem', marginBottom: '1rem',
}, },
}, },
percentageContainer: {
display: 'flex',
justifyContent: 'center',
margin: '1rem 0',
},
projectIcon: { projectIcon: {
margin: '2rem 0', margin: '2rem 0',
[theme.breakpoints.down('sm')]: { [theme.breakpoints.down('sm')]: {
@ -35,7 +40,7 @@ export const useStyles = makeStyles(theme => ({
infoSection: { infoSection: {
margin: '0', margin: '0',
textAlign: 'center', textAlign: 'center',
marginBottom: '1.5rem', marginBottom: '1rem',
backgroundColor: '#fff', backgroundColor: '#fff',
borderRadius: '10px', borderRadius: '10px',
width: '100%', width: '100%',

View File

@ -3,9 +3,9 @@ import { Link } from 'react-router-dom';
import ArrowForwardIcon from '@material-ui/icons/ArrowForward'; import ArrowForwardIcon from '@material-ui/icons/ArrowForward';
import classnames from 'classnames'; import classnames from 'classnames';
import { ReactComponent as ProjectIcon } from '../../../../assets/icons/projectIcon.svg';
import { useCommonStyles } from '../../../../common.styles'; import { useCommonStyles } from '../../../../common.styles';
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
import PercentageCircle from '../../../common/PercentageCircle/PercentageCircle';
interface IProjectInfoProps { interface IProjectInfoProps {
id: string; id: string;
@ -34,8 +34,8 @@ const ProjectInfo = ({
<aside> <aside>
<div className={styles.projectInfo}> <div className={styles.projectInfo}>
<div className={styles.infoSection}> <div className={styles.infoSection}>
<div data-loading> <div data-loading className={styles.percentageContainer}>
<ProjectIcon className={styles.projectIcon} /> <PercentageCircle percentage={health} />
</div> </div>
<p className={styles.subtitle} data-loading> <p className={styles.subtitle} data-loading>
Overall health rating Overall health rating

View File

@ -21,9 +21,13 @@ const ProjectOverview = ({projectId}: ProjectOverviewProps) => {
health={health} health={health}
featureCount={features?.length} featureCount={features?.length}
/> />
<ProjectFeatureToggles features={features} loading={loading} /> <div className={styles.projectToggles}>
<ProjectFeatureToggles
features={features}
loading={loading}
/>
</div>
</div> </div>
</div> </div>
); );
}; };

View File

@ -8,7 +8,6 @@ export interface IFeatureToggleListItem {
export interface IEnvironments { export interface IEnvironments {
name: string; name: string;
displayName: string;
enabled: boolean; enabled: boolean;
} }
@ -16,7 +15,7 @@ export interface IFeatureToggle {
stale: boolean; stale: boolean;
archived: boolean; archived: boolean;
createdAt: string; createdAt: string;
lastSeenAt: string; lastSeenAt: Date;
description: string; description: string;
environments: IFeatureEnvironment[]; environments: IFeatureEnvironment[];
name: string; name: string;

View File

@ -2176,6 +2176,13 @@
"@types/history" "*" "@types/history" "*"
"@types/react" "*" "@types/react" "*"
"@types/react-timeago@^4.1.3":
version "4.1.3"
resolved "https://registry.yarnpkg.com/@types/react-timeago/-/react-timeago-4.1.3.tgz#957baaa9f8ea98457ee63b6a57b57a616066ba35"
integrity sha512-XaaMBzuXLw7lxPPDs/fenlohcf3NDqM5qP4oOL/Meu+Hb1QChW4Igw/SruS1llEqch18RQB3wDTIwvqq4nivvw==
dependencies:
"@types/react" "*"
"@types/react-transition-group@^4.2.0": "@types/react-transition-group@^4.2.0":
version "4.4.1" version "4.4.1"
resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz" resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz"