1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

fix: make toggle list sortable inside a project (#436)

* fix: make toggle list sortable inside a project

* fix: minor cleanup
This commit is contained in:
Ivar Conradi Østhus 2021-10-15 11:20:14 +02:00 committed by GitHub
parent 4230fc98ed
commit d42d412bc8
13 changed files with 233 additions and 159 deletions

View File

@ -37,6 +37,14 @@ const PaginateUI = ({
}
}, [matches]);
useEffect(() => {
if(pageIndex === 0 && start !== 0) {
setStart(0);
setLimit(STARTLIMIT);
}
}, [pageIndex, start])
return (
<ConditionallyRender
condition={pages.length > 1}

View File

@ -2,9 +2,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import { MenuItem, Typography } from '@material-ui/core';
import { HourglassEmpty, HourglassFull } from '@material-ui/icons';
import { MenuItemWithIcon } from '../../../common';
// import { HourglassEmpty, HourglassFull } from '@material-ui/icons';
// import { MenuItemWithIcon } from '../../../common';
import DropdownMenu from '../../../common/DropdownMenu/DropdownMenu';
import ProjectSelect from '../../../common/ProjectSelect';
import { useStyles } from './styles';
@ -53,6 +52,7 @@ const FeatureToggleListActions = ({
</MenuItem>
));
/*
const renderMetricsOptions = () => [
<MenuItemWithIcon
style={{ fontSize: '14px' }}
@ -71,12 +71,14 @@ const FeatureToggleListActions = ({
key={2}
/>,
];
*/
return (
<div className={styles.actions} ref={ref}>
<Typography variant="body2" data-loading>
Sorted by:
</Typography>
{/* }
<DropdownMenu
id={'metric'}
label={`Last ${settings.showLastHour ? 'hour' : 'minute'}`}
@ -87,6 +89,7 @@ const FeatureToggleListActions = ({
style={{ textTransform: 'lowercase', fontWeight: 'normal' }}
data-loading
/>
{*/}
<DropdownMenu
id={'sorting'}
label={`By ${settings.sort}`}

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import { Chip, IconButton, ListItem } from '@material-ui/core';
import { Chip, IconButton, ListItem, Tooltip } from '@material-ui/core';
import { Undo } from '@material-ui/icons';
import TimeAgo from 'react-timeago';
@ -58,9 +58,11 @@ const FeatureToggleListItem = ({
commonStyles.truncate
)}
>
<span className={commonStyles.toggleName}>
{name}&nbsp;
</span>
<Tooltip title={description}>
<span className={commonStyles.toggleName}>
{name}&nbsp;
</span>
</Tooltip>
<span className={styles.listItemToggle}>
</span>
<small>
@ -74,9 +76,11 @@ const FeatureToggleListItem = ({
</Link>
} elseShow={
<>
<span className={commonStyles.toggleName}>
{name}&nbsp;
</span>
<Tooltip title={description}>
<span className={commonStyles.toggleName}>
{name}&nbsp;
z </span>
</Tooltip>
<span className={styles.listItemToggle}>
</span>
<small>
@ -100,7 +104,9 @@ const FeatureToggleListItem = ({
)}
>
<Status stale={stale} showActive={false} />
<Chip color="primary" variant="outlined" className={styles.typeChip} style={{marginLeft: '8px'}} title={`Project: ${project}`} label={project}/>
<Link to={`/projects/${project}`} style={{textDecoration: 'none'}}>
<Chip color="primary" variant="outlined" className={styles.typeChip} style={{marginLeft: '8px' }} title={`Project: ${project}`} label={project}/>
</Link>
</span>
<ConditionallyRender
condition={revive}

View File

@ -13,12 +13,14 @@ export const useStyles = makeStyles(theme => ({
listItemType: {
width: '40px',
textAlign: 'center',
marginRight: '0',
flexShrink: '0',
},
listItemSvg: {
fill: theme.palette.icons.lightGrey,
},
listItemLink: {
marginLeft: '10px',
marginLeft: '0.25rem',
minWidth: '0',
},
listItemStrategies: {

View File

@ -23,7 +23,7 @@ exports[`renders correctly with one feature 1`] = `
"fontSize": "0.8rem",
}
}
title="No usage reported"
title="No usage reported from connected applications"
>
<span
style={
@ -68,7 +68,15 @@ exports[`renders correctly with one feature 1`] = `
onClick={[Function]}
>
<span
aria-describedby={null}
className="toggleName"
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="another's description"
>
Another
 
@ -96,23 +104,33 @@ exports[`renders correctly with one feature 1`] = `
<span
className="makeStyles-listItemStrategies-6 hideLt920"
>
<div
className="MuiChip-root MuiChip-colorPrimary MuiChip-outlined MuiChip-outlinedPrimary"
onKeyDown={[Function]}
onKeyUp={[Function]}
<a
href="/projects/default"
onClick={[Function]}
style={
Object {
"marginLeft": "8px",
"textDecoration": "none",
}
}
title="Project: default"
>
<span
className="MuiChip-label"
<div
className="MuiChip-root MuiChip-colorPrimary MuiChip-outlined MuiChip-outlinedPrimary"
onKeyDown={[Function]}
onKeyUp={[Function]}
style={
Object {
"marginLeft": "8px",
}
}
title="Project: default"
>
default
</span>
</div>
<span
className="MuiChip-label"
>
default
</span>
</div>
</a>
</span>
</li>
`;
@ -140,7 +158,7 @@ exports[`renders correctly with one feature without permission 1`] = `
"fontSize": "0.8rem",
}
}
title="No usage reported"
title="No usage reported from connected applications"
>
<span
style={
@ -185,7 +203,15 @@ exports[`renders correctly with one feature without permission 1`] = `
onClick={[Function]}
>
<span
aria-describedby={null}
className="toggleName"
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
title="another's description"
>
Another
 
@ -213,21 +239,31 @@ exports[`renders correctly with one feature without permission 1`] = `
<span
className="makeStyles-listItemStrategies-6 hideLt920"
>
<div
className="MuiChip-root MuiChip-colorPrimary MuiChip-outlined MuiChip-outlinedPrimary"
onKeyDown={[Function]}
onKeyUp={[Function]}
<a
href="/projects/undefined"
onClick={[Function]}
style={
Object {
"marginLeft": "8px",
"textDecoration": "none",
}
}
title="Project: undefined"
>
<span
className="MuiChip-label"
/>
</div>
<div
className="MuiChip-root MuiChip-colorPrimary MuiChip-outlined MuiChip-outlinedPrimary"
onKeyDown={[Function]}
onKeyUp={[Function]}
style={
Object {
"marginLeft": "8px",
}
}
title="Project: undefined"
>
<span
className="MuiChip-label"
/>
</div>
</a>
</span>
</li>
`;

View File

@ -85,60 +85,6 @@ exports[`renders correctly with one feature 1`] = `
>
Sorted by:
</p>
<button
aria-controls="metric"
aria-haspopup="true"
className="MuiButtonBase-root MuiButton-root MuiButton-text"
data-loading={true}
disabled={false}
id="metric"
onBlur={[Function]}
onClick={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
style={
Object {
"fontWeight": "normal",
"textTransform": "lowercase",
}
}
tabIndex={0}
title="Metric interval"
type="button"
>
<span
className="MuiButton-label"
>
Last minute
<span
className="MuiButton-endIcon MuiButton-iconSizeMedium"
>
<span
aria-hidden={true}
className="material-icons MuiIcon-root"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
</svg>
</span>
</span>
</span>
</button>
<button
aria-controls="sorting"
aria-haspopup="true"
@ -336,63 +282,6 @@ exports[`renders correctly with one feature without permissions 1`] = `
>
Sorted by:
</p>
<button
aria-controls="metric"
aria-haspopup="true"
className="MuiButtonBase-root MuiButton-root MuiButton-text"
data-loading={true}
disabled={false}
id="metric"
onBlur={[Function]}
onClick={[Function]}
onDragLeave={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseLeave={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchMove={[Function]}
onTouchStart={[Function]}
style={
Object {
"fontWeight": "normal",
"textTransform": "lowercase",
}
}
tabIndex={0}
title="Metric interval"
type="button"
>
<span
className="MuiButton-label"
>
Last minute
<span
className="MuiButton-endIcon MuiButton-iconSizeMedium"
>
<span
aria-hidden={true}
className="material-icons MuiIcon-root"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
</svg>
</span>
</span>
</span>
<span
className="MuiTouchRipple-root"
/>
</button>
<button
aria-controls="sorting"
aria-haspopup="true"

View File

@ -14,6 +14,9 @@ export const useStyles = makeStyles(theme => ({
color: theme.palette.grey[600],
borderBottom: '1px solid ' + theme.palette.grey[200],
},
tableCellHeaderSortable: {
cursor: 'pointer',
},
tableCellStatus: {
width: '50px',
},
@ -26,6 +29,11 @@ export const useStyles = makeStyles(theme => ({
display: 'none',
},
},
tableCellCreated: {
[theme.breakpoints.down('sm')]: {
display: 'none',
},
},
tableCellType: {
width: '32px',
alignItems: 'center',

View File

@ -1,3 +1,4 @@
import { useState } from 'react';
import {
Table,
TableBody,
@ -22,14 +23,86 @@ interface IFeatureToggleListNewProps {
projectId: string;
}
//@ts-ignore
const sortList = (list, sortOpt) => {
if(!list) {
return list;
}
if(!sortOpt.field) {
return list;
}
if (sortOpt.type === 'string') {
//@ts-ignore
return list.sort((a, b) => {
const fieldA = a[sortOpt.field]?.toUpperCase();
const fieldB = b[sortOpt.field]?.toUpperCase();
const direction = sortOpt.direction;
if (fieldA < fieldB) {
return direction === 0 ? -1 : 1;
}
if (fieldA > fieldB) {
return direction === 0 ? 1 : -1;
}
return 0;
})
}
if (sortOpt.type === 'date') {
//@ts-ignore
return list.sort((a, b) => {
const fieldA = new Date(a[sortOpt.field]);
const fieldB = new Date(b[sortOpt.field]);
if (fieldA < fieldB) {
return sortOpt.direction === 0 ? 1 : -1;
}
if (fieldA > fieldB) {
return sortOpt.direction === 0 ? -1 : 1;
}
return 0;
})
}
return list;
}
const FeatureToggleListNew = ({
features,
loading,
projectId,
}: IFeatureToggleListNewProps) => {
const styles = useStyles();
const [sortOpt, setSortOpt] = useState({
field: 'name',
type: 'string',
direction: 0,
});
const [sortedFeatures, setSortedFeatures] = useState(sortList([...features], sortOpt));
const { page, pages, nextPage, prevPage, setPageIndex, pageIndex } =
usePagination(features, 50);
usePagination(sortedFeatures, 50);
const updateSort = (field: string) => {
let newSortOpt;
if(field === sortOpt.field) {
newSortOpt = {...sortOpt, direction: (sortOpt.direction + 1) % 2};
}
else if(['createdAt', 'lastSeenAt'].includes(field)) {
newSortOpt = {
field,
type: 'date',
direction: 0
};
} else {
newSortOpt = {
field,
type: 'string',
direction: 0
};
}
setSortOpt(newSortOpt);
setSortedFeatures(sortList([...features], newSortOpt));
setPageIndex(0);
};
const getEnvironments = () => {
if (features.length > 0) {
@ -55,6 +128,7 @@ const FeatureToggleListNew = ({
type={feature.type}
environments={feature.environments}
projectId={projectId}
createdAt={new Date()}
/>
);
});
@ -69,6 +143,7 @@ const FeatureToggleListNew = ({
environments={feature.environments}
projectId={projectId}
lastSeenAt={feature.lastSeenAt}
createdAt={feature.createdAt}
/>
);
});
@ -83,31 +158,45 @@ const FeatureToggleListNew = ({
className={classnames(
styles.tableCell,
styles.tableCellStatus,
styles.tableCellHeader
styles.tableCellHeader,
styles.tableCellHeaderSortable,
)}
align="left"
>
<span data-loading>Status</span>
<span data-loading onClick={() => updateSort('lastSeenAt')}>Status</span>
</TableCell>
<TableCell
className={classnames(
styles.tableCell,
styles.tableCellType,
styles.tableCellHeader
styles.tableCellHeader,
styles.tableCellHeaderSortable,
)}
align="center"
>
<span data-loading>Type</span>
<span data-loading onClick={() => updateSort('type')}>Type</span>
</TableCell>
<TableCell
className={classnames(
styles.tableCell,
styles.tableCellName,
styles.tableCellHeader
styles.tableCellHeader,
styles.tableCellHeaderSortable,
)}
align="left"
>
<span data-loading>Name</span>
<span data-loading onClick={() => updateSort('name')}>Name</span>
</TableCell>
<TableCell
className={classnames(
styles.tableCell,
styles.tableCellCreated,
styles.tableCellHeader,
styles.tableCellHeaderSortable,
)}
align="left"
>
<span data-loading onClick={() => updateSort('createdAt')}>Created</span>
</TableCell>
{getEnvironments().map((env: any) => {
return (
@ -116,7 +205,8 @@ const FeatureToggleListNew = ({
className={classnames(
styles.tableCell,
styles.tableCellEnv,
styles.tableCellHeader
styles.tableCellHeader,
styles.tableCellHeaderSortable,
)}
align="center"
>

View File

@ -0,0 +1,25 @@
import { Tooltip } from '@material-ui/core';
import { connect } from 'react-redux';
import { formatDateWithLocale, formatFullDateTimeWithLocale } from '../../../common/util';
interface CreatedAtProps {
time: Date;
//@ts-ignore
location: any;
}
const CreatedAt = ({time, location}: CreatedAtProps) => {
return (
<Tooltip title={`Created at ${formatFullDateTimeWithLocale(time, location.locale)}`}>
<span>
{formatDateWithLocale(time, location.locale)}
</span>
</Tooltip>
);
}
const mapStateToProps = (state: any) => ({
location: state.settings.toJS().location,
});
export default connect(mapStateToProps)(CreatedAt);

View File

@ -16,6 +16,7 @@ import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
import FeatureStatus from '../../FeatureView2/FeatureStatus/FeatureStatus';
import FeatureType from '../../FeatureView2/FeatureType/FeatureType';
import classNames from 'classnames';
import CreatedAt from './CreatedAt';
interface IFeatureToggleListNewItemProps {
name: string;
@ -23,6 +24,7 @@ interface IFeatureToggleListNewItemProps {
environments: IFeatureEnvironment[];
projectId: string;
lastSeenAt?: Date;
createdAt: Date;
}
const FeatureToggleListNewItem = ({
@ -31,6 +33,7 @@ const FeatureToggleListNewItem = ({
type,
environments,
projectId,
createdAt,
}: IFeatureToggleListNewItemProps) => {
const { toast, setToastData } = useToast();
const { toggleFeatureByEnvironment } = useToggleFeatureByEnv(
@ -87,6 +90,10 @@ const FeatureToggleListNewItem = ({
styles.tableCell, styles.tableCellName)} align="left" onClick={onClick}>
<span data-loading>{name}</span>
</TableCell>
<TableCell className={classNames(
styles.tableCell, styles.tableCellCreated)} align="left" onClick={onClick}>
<CreatedAt time={createdAt} />
</TableCell>
{environments.map((env: IEnvironments) => {

View File

@ -83,7 +83,7 @@ const FeatureStatus = ({ lastSeenAt }: FeatureStatusProps) => {
) => {
return (
<Wrapper
toolTip={`Last usage reported ${value} ${unit} ${suffix}`}
toolTip={`Last usage reported ${value} ${unit}${value !== 1 ? 's' : ''} ${suffix}`}
color={getColor(unit)}
>
{value}
@ -94,7 +94,7 @@ const FeatureStatus = ({ lastSeenAt }: FeatureStatusProps) => {
/>
}
elseShow={
<Wrapper toolTip="No usage reported" color={getColor()}>
<Wrapper toolTip="No usage reported from connected applications" color={getColor()}>
<span style={{ fontSize: '1.4rem' }}></span>
</Wrapper>
}

View File

@ -14,7 +14,7 @@ export const useStyles = makeStyles(theme => ({
width: 'inherit',
},
},
bodyClass: { padding: '0.5rem 2rem' },
bodyClass: { padding: '0.5rem 1rem' },
header: {
padding: '1rem',
},

View File

@ -14,8 +14,8 @@ export interface IEnvironments {
export interface IFeatureToggle {
stale: boolean;
archived: boolean;
createdAt: string;
lastSeenAt: Date;
createdAt: Date;
lastSeenAt?: Date;
description: string;
environments: IFeatureEnvironment[];
name: string;