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:
parent
4230fc98ed
commit
d42d412bc8
@ -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}
|
||||
|
@ -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}`}
|
||||
|
@ -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
|
||||
)}
|
||||
>
|
||||
<Tooltip title={description}>
|
||||
<span className={commonStyles.toggleName}>
|
||||
{name}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className={styles.listItemToggle}>
|
||||
</span>
|
||||
<small>
|
||||
@ -74,9 +76,11 @@ const FeatureToggleListItem = ({
|
||||
</Link>
|
||||
} elseShow={
|
||||
<>
|
||||
<Tooltip title={description}>
|
||||
<span className={commonStyles.toggleName}>
|
||||
{name}
|
||||
</span>
|
||||
z </span>
|
||||
</Tooltip>
|
||||
<span className={styles.listItemToggle}>
|
||||
</span>
|
||||
<small>
|
||||
@ -100,7 +104,9 @@ const FeatureToggleListItem = ({
|
||||
)}
|
||||
>
|
||||
<Status stale={stale} showActive={false} />
|
||||
<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}
|
||||
|
@ -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: {
|
||||
|
@ -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
|
||||
|
||||
@ -95,6 +103,15 @@ exports[`renders correctly with one feature 1`] = `
|
||||
</span>
|
||||
<span
|
||||
className="makeStyles-listItemStrategies-6 hideLt920"
|
||||
>
|
||||
<a
|
||||
href="/projects/default"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"textDecoration": "none",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="MuiChip-root MuiChip-colorPrimary MuiChip-outlined MuiChip-outlinedPrimary"
|
||||
@ -113,6 +130,7 @@ exports[`renders correctly with one feature 1`] = `
|
||||
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
|
||||
|
||||
@ -212,6 +238,15 @@ exports[`renders correctly with one feature without permission 1`] = `
|
||||
</span>
|
||||
<span
|
||||
className="makeStyles-listItemStrategies-6 hideLt920"
|
||||
>
|
||||
<a
|
||||
href="/projects/undefined"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"textDecoration": "none",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="MuiChip-root MuiChip-colorPrimary MuiChip-outlined MuiChip-outlinedPrimary"
|
||||
@ -228,6 +263,7 @@ exports[`renders correctly with one feature without permission 1`] = `
|
||||
className="MuiChip-label"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</li>
|
||||
`;
|
||||
|
@ -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"
|
||||
|
@ -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',
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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);
|
@ -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) => {
|
||||
|
@ -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>
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export const useStyles = makeStyles(theme => ({
|
||||
width: 'inherit',
|
||||
},
|
||||
},
|
||||
bodyClass: { padding: '0.5rem 2rem' },
|
||||
bodyClass: { padding: '0.5rem 1rem' },
|
||||
header: {
|
||||
padding: '1rem',
|
||||
},
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user