mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
remove dead code
This commit is contained in:
parent
88f2bef1be
commit
d50edfa294
@ -1,178 +0,0 @@
|
|||||||
import { Dispatch, SetStateAction, useContext, VFC } from 'react';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { List, ListItem } from '@mui/material';
|
|
||||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
|
||||||
import { IFlags } from 'interfaces/uiConfig';
|
|
||||||
import { SearchField } from 'component/common/SearchField/SearchField';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
|
||||||
import AccessContext from 'contexts/AccessContext';
|
|
||||||
import { IFeaturesFilter } from 'hooks/useFeaturesFilter';
|
|
||||||
import { FeatureToggleListItem } from './FeatureToggleListItem/FeatureToggleListItem';
|
|
||||||
import { FeatureToggleListActions } from './FeatureToggleListActions/FeatureToggleListActions';
|
|
||||||
import { CreateFeatureButton } from '../CreateFeatureButton/CreateFeatureButton';
|
|
||||||
import { IFeaturesSort } from 'hooks/useFeaturesSort';
|
|
||||||
import { FeatureSchema } from 'openapi';
|
|
||||||
import { useStyles } from './styles';
|
|
||||||
|
|
||||||
interface IFeatureToggleListProps {
|
|
||||||
features: FeatureSchema[];
|
|
||||||
loading?: boolean;
|
|
||||||
flags?: IFlags;
|
|
||||||
filter: IFeaturesFilter;
|
|
||||||
setFilter: Dispatch<SetStateAction<IFeaturesFilter>>;
|
|
||||||
sort: IFeaturesSort;
|
|
||||||
setSort: Dispatch<SetStateAction<IFeaturesSort>>;
|
|
||||||
onRevive?: (feature: string) => void;
|
|
||||||
inProject?: boolean;
|
|
||||||
isArchive?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadingFeaturesPlaceholder: FeatureSchema[] = Array(10)
|
|
||||||
.fill({
|
|
||||||
createdAt: '2021-03-19T09:16:21.329Z',
|
|
||||||
description: ' ',
|
|
||||||
enabled: true,
|
|
||||||
lastSeenAt: '2021-03-24T10:46:38.036Z',
|
|
||||||
name: '',
|
|
||||||
project: 'default',
|
|
||||||
stale: true,
|
|
||||||
strategies: [],
|
|
||||||
variants: [],
|
|
||||||
type: 'release',
|
|
||||||
archived: false,
|
|
||||||
environments: [],
|
|
||||||
impressionData: false,
|
|
||||||
})
|
|
||||||
.map((feature, index) => ({ ...feature, name: `${index}` })); // ID for React key
|
|
||||||
|
|
||||||
export const FeatureToggleList: VFC<IFeatureToggleListProps> = ({
|
|
||||||
features,
|
|
||||||
onRevive,
|
|
||||||
inProject,
|
|
||||||
isArchive,
|
|
||||||
loading,
|
|
||||||
flags,
|
|
||||||
filter,
|
|
||||||
setFilter,
|
|
||||||
sort,
|
|
||||||
setSort,
|
|
||||||
}) => {
|
|
||||||
const { hasAccess } = useContext(AccessContext);
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const smallScreen = useMediaQuery('(max-width:800px)');
|
|
||||||
const mobileView = useMediaQuery('(max-width:600px)');
|
|
||||||
|
|
||||||
const setFilterQuery = (v: string) => {
|
|
||||||
const query = v && typeof v === 'string' ? v.trim() : '';
|
|
||||||
setFilter(prev => ({ ...prev, query }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderFeatures = () => {
|
|
||||||
if (loading) {
|
|
||||||
return loadingFeaturesPlaceholder.map(feature => (
|
|
||||||
<FeatureToggleListItem
|
|
||||||
key={feature.name}
|
|
||||||
feature={feature}
|
|
||||||
onRevive={onRevive}
|
|
||||||
hasAccess={hasAccess}
|
|
||||||
className={'skeleton'}
|
|
||||||
flags={flags}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={features.length > 0}
|
|
||||||
show={features.map(feature => (
|
|
||||||
<FeatureToggleListItem
|
|
||||||
key={feature.name}
|
|
||||||
feature={feature}
|
|
||||||
onRevive={onRevive}
|
|
||||||
hasAccess={hasAccess}
|
|
||||||
flags={flags}
|
|
||||||
inProject={inProject}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
elseShow={
|
|
||||||
<ListItem className={styles.emptyStateListItem}>
|
|
||||||
No archived features.
|
|
||||||
</ListItem>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const searchResultsHeader = filter.query
|
|
||||||
? ` (${features.length} matches)`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const headerTitle = isArchive
|
|
||||||
? inProject
|
|
||||||
? `Project Archived Features ${searchResultsHeader}`
|
|
||||||
: `Archived Features ${searchResultsHeader}`
|
|
||||||
: `Features ${searchResultsHeader}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
className={classnames(styles.searchBarContainer, {
|
|
||||||
dense: inProject,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<SearchField
|
|
||||||
initialValue={filter.query}
|
|
||||||
updateValue={setFilterQuery}
|
|
||||||
showValueChip={!mobileView}
|
|
||||||
className={classnames(styles.searchBar, {
|
|
||||||
skeleton: loading,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={!mobileView && !isArchive}
|
|
||||||
show={<Link to="/archive">Archive</Link>}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<PageContent
|
|
||||||
header={
|
|
||||||
<PageHeader
|
|
||||||
loading={loading}
|
|
||||||
title={headerTitle}
|
|
||||||
actions={
|
|
||||||
<div className={styles.actionsContainer}>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={!smallScreen}
|
|
||||||
show={
|
|
||||||
<FeatureToggleListActions
|
|
||||||
filter={filter}
|
|
||||||
setFilter={setFilter}
|
|
||||||
sort={sort}
|
|
||||||
setSort={setSort}
|
|
||||||
loading={loading}
|
|
||||||
inProject={inProject}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={!isArchive}
|
|
||||||
show={
|
|
||||||
<CreateFeatureButton
|
|
||||||
filter={filter}
|
|
||||||
loading={Boolean(loading)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<List>{renderFeatures()}</List>
|
|
||||||
</PageContent>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,99 +0,0 @@
|
|||||||
import { Dispatch, MouseEventHandler, SetStateAction, VFC } from 'react';
|
|
||||||
import { MenuItem, Typography } from '@mui/material';
|
|
||||||
import DropdownMenu from 'component/common/DropdownMenu/DropdownMenu';
|
|
||||||
import ProjectSelect from 'component/common/ProjectSelect/ProjectSelect';
|
|
||||||
import useLoading from 'hooks/useLoading';
|
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import {
|
|
||||||
createFeaturesFilterSortOptions,
|
|
||||||
FeaturesSortType,
|
|
||||||
IFeaturesSort,
|
|
||||||
} from 'hooks/useFeaturesSort';
|
|
||||||
import { useStyles } from './styles';
|
|
||||||
import { IFeaturesFilter } from 'hooks/useFeaturesFilter';
|
|
||||||
|
|
||||||
let sortOptions = createFeaturesFilterSortOptions();
|
|
||||||
|
|
||||||
interface IFeatureToggleListActionsProps {
|
|
||||||
filter: IFeaturesFilter;
|
|
||||||
setFilter: Dispatch<SetStateAction<IFeaturesFilter>>;
|
|
||||||
sort: IFeaturesSort;
|
|
||||||
setSort: Dispatch<SetStateAction<IFeaturesSort>>;
|
|
||||||
loading?: boolean;
|
|
||||||
inProject?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FeatureToggleListActions: VFC<IFeatureToggleListActionsProps> = ({
|
|
||||||
filter,
|
|
||||||
setFilter,
|
|
||||||
sort,
|
|
||||||
setSort,
|
|
||||||
loading = false,
|
|
||||||
inProject,
|
|
||||||
}) => {
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const { uiConfig } = useUiConfig();
|
|
||||||
const ref = useLoading(loading);
|
|
||||||
|
|
||||||
const handleSort: MouseEventHandler = e => {
|
|
||||||
const type = (e.target as Element)
|
|
||||||
.getAttribute('data-target')
|
|
||||||
?.trim() as FeaturesSortType;
|
|
||||||
if (type) {
|
|
||||||
setSort(prev => ({ ...prev, type }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectedOption =
|
|
||||||
sortOptions.find(o => o.type === sort.type) || sortOptions[0];
|
|
||||||
|
|
||||||
if (inProject) {
|
|
||||||
sortOptions = sortOptions.filter(option => option.type !== 'project');
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderSortingOptions = () =>
|
|
||||||
sortOptions.map(option => (
|
|
||||||
<MenuItem
|
|
||||||
style={{ fontSize: '14px' }}
|
|
||||||
key={option.type}
|
|
||||||
disabled={option.type === sort.type}
|
|
||||||
data-target={option.type}
|
|
||||||
>
|
|
||||||
{option.name}
|
|
||||||
</MenuItem>
|
|
||||||
));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.actions} ref={ref}>
|
|
||||||
<Typography variant="body2" data-loading>
|
|
||||||
Sorted by:
|
|
||||||
</Typography>
|
|
||||||
<DropdownMenu
|
|
||||||
id={'sorting'}
|
|
||||||
label={`By ${selectedOption.name}`}
|
|
||||||
callback={handleSort}
|
|
||||||
renderOptions={renderSortingOptions}
|
|
||||||
title="Sort by"
|
|
||||||
style={{ textTransform: 'lowercase', fontWeight: 'normal' }}
|
|
||||||
data-loading
|
|
||||||
/>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={uiConfig.flags.P && !inProject}
|
|
||||||
show={
|
|
||||||
<ProjectSelect
|
|
||||||
currentProjectId={filter.project}
|
|
||||||
updateCurrentProject={project =>
|
|
||||||
setFilter(prev => ({ ...prev, project }))
|
|
||||||
}
|
|
||||||
style={{
|
|
||||||
textTransform: 'lowercase',
|
|
||||||
fontWeight: 'normal',
|
|
||||||
}}
|
|
||||||
data-loading
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,12 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()({
|
|
||||||
actions: {
|
|
||||||
'& > *': {
|
|
||||||
margin: '0 0.25rem',
|
|
||||||
},
|
|
||||||
marginRight: '0.25rem',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,239 +0,0 @@
|
|||||||
import React, { useMemo } from 'react';
|
|
||||||
import { basePath } from 'utils/formatPath';
|
|
||||||
import { createPersistentGlobalStateHook } from './usePersistentGlobalState';
|
|
||||||
import {
|
|
||||||
expired,
|
|
||||||
getDiffInDays,
|
|
||||||
toggleExpiryByTypeMap,
|
|
||||||
} from 'component/Reporting/utils';
|
|
||||||
import { FeatureSchema } from 'openapi';
|
|
||||||
|
|
||||||
export type FeaturesSortType =
|
|
||||||
| 'name'
|
|
||||||
| 'expired'
|
|
||||||
| 'type'
|
|
||||||
| 'enabled'
|
|
||||||
| 'stale'
|
|
||||||
| 'created'
|
|
||||||
| 'archived'
|
|
||||||
| 'last-seen'
|
|
||||||
| 'status'
|
|
||||||
| 'project';
|
|
||||||
|
|
||||||
export interface IFeaturesSort {
|
|
||||||
type: FeaturesSortType;
|
|
||||||
desc?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFeaturesSortOutput {
|
|
||||||
sort: IFeaturesSort;
|
|
||||||
sorted: FeatureSchema[];
|
|
||||||
setSort: React.Dispatch<React.SetStateAction<IFeaturesSort>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFeaturesFilterSortOption {
|
|
||||||
type: FeaturesSortType;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the features sort state globally, and in localStorage.
|
|
||||||
// When changing the format of IFeaturesSort, change the version as well.
|
|
||||||
const useFeaturesSortState = createPersistentGlobalStateHook<IFeaturesSort>(
|
|
||||||
`${basePath}:useFeaturesSort:v1`,
|
|
||||||
{ type: 'name' }
|
|
||||||
);
|
|
||||||
|
|
||||||
export const useFeaturesSort = (
|
|
||||||
features: FeatureSchema[]
|
|
||||||
): IFeaturesSortOutput => {
|
|
||||||
const [sort, setSort] = useFeaturesSortState();
|
|
||||||
|
|
||||||
const sorted = useMemo(() => {
|
|
||||||
return sortFeatures(features, sort);
|
|
||||||
}, [features, sort]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
setSort,
|
|
||||||
sort,
|
|
||||||
sorted,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createFeaturesFilterSortOptions =
|
|
||||||
(): IFeaturesFilterSortOption[] => {
|
|
||||||
return [
|
|
||||||
{ type: 'name', name: 'Name' },
|
|
||||||
{ type: 'type', name: 'Type' },
|
|
||||||
{ type: 'enabled', name: 'Enabled' },
|
|
||||||
{ type: 'stale', name: 'Stale' },
|
|
||||||
{ type: 'status', name: 'Status' },
|
|
||||||
{ type: 'created', name: 'Created' },
|
|
||||||
{ type: 'archived', name: 'Archived' },
|
|
||||||
{ type: 'last-seen', name: 'Last seen' },
|
|
||||||
{ type: 'project', name: 'Project' },
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortAscendingFeatures = (
|
|
||||||
features: FeatureSchema[],
|
|
||||||
sort: IFeaturesSort
|
|
||||||
): FeatureSchema[] => {
|
|
||||||
switch (sort.type) {
|
|
||||||
case 'enabled':
|
|
||||||
return sortByEnabled(features);
|
|
||||||
case 'stale':
|
|
||||||
return sortByStale(features);
|
|
||||||
case 'created':
|
|
||||||
return sortByCreated(features);
|
|
||||||
case 'archived':
|
|
||||||
return sortByArchived(features);
|
|
||||||
case 'last-seen':
|
|
||||||
return sortByLastSeen(features);
|
|
||||||
case 'name':
|
|
||||||
return sortByName(features);
|
|
||||||
case 'project':
|
|
||||||
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: FeatureSchema[],
|
|
||||||
sort: IFeaturesSort
|
|
||||||
): FeatureSchema[] => {
|
|
||||||
const sorted = sortAscendingFeatures(features, sort);
|
|
||||||
|
|
||||||
if (sort.desc) {
|
|
||||||
return [...sorted].reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
return sorted;
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortByEnabled = (
|
|
||||||
features: Readonly<FeatureSchema[]>
|
|
||||||
): FeatureSchema[] => {
|
|
||||||
return [...features].sort((a, b) =>
|
|
||||||
a.enabled === b.enabled ? 0 : a.enabled ? -1 : 1
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortByStale = (features: Readonly<FeatureSchema[]>): FeatureSchema[] => {
|
|
||||||
return [...features].sort((a, b) =>
|
|
||||||
a.stale === b.stale ? 0 : a.stale ? -1 : 1
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortByLastSeen = (
|
|
||||||
features: Readonly<FeatureSchema[]>
|
|
||||||
): FeatureSchema[] => {
|
|
||||||
return [...features].sort((a, b) =>
|
|
||||||
a.lastSeenAt && b.lastSeenAt
|
|
||||||
? compareNullableDates(b.lastSeenAt, a.lastSeenAt)
|
|
||||||
: a.lastSeenAt
|
|
||||||
? -1
|
|
||||||
: b.lastSeenAt
|
|
||||||
? 1
|
|
||||||
: compareNullableDates(b.createdAt, a.createdAt)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortByCreated = (
|
|
||||||
features: Readonly<FeatureSchema[]>
|
|
||||||
): FeatureSchema[] => {
|
|
||||||
return [...features].sort((a, b) =>
|
|
||||||
compareNullableDates(b.createdAt, a.createdAt)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortByArchived = (
|
|
||||||
features: Readonly<FeatureSchema[]>
|
|
||||||
): FeatureSchema[] => {
|
|
||||||
return [...features].sort((a, b) =>
|
|
||||||
compareNullableDates(b.archivedAt, a.archivedAt)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortByName = (features: Readonly<FeatureSchema[]>): FeatureSchema[] => {
|
|
||||||
return [...features].sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortByProject = (
|
|
||||||
features: Readonly<FeatureSchema[]>
|
|
||||||
): FeatureSchema[] => {
|
|
||||||
return [...features].sort((a, b) =>
|
|
||||||
a.project && b.project
|
|
||||||
? a.project.localeCompare(b.project)
|
|
||||||
: a.project
|
|
||||||
? 1
|
|
||||||
: b.project
|
|
||||||
? -1
|
|
||||||
: 0
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortByType = (features: Readonly<FeatureSchema[]>): FeatureSchema[] => {
|
|
||||||
return [...features].sort((a, b) =>
|
|
||||||
a.type && b.type
|
|
||||||
? a.type.localeCompare(b.type)
|
|
||||||
: a.type
|
|
||||||
? 1
|
|
||||||
: b.type
|
|
||||||
? -1
|
|
||||||
: 0
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const compareNullableDates = (
|
|
||||||
a: Date | null | undefined,
|
|
||||||
b: Date | null | undefined
|
|
||||||
): number => {
|
|
||||||
return a && b ? a?.getTime?.() - b?.getTime?.() : a ? 1 : b ? -1 : 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortByExpired = (
|
|
||||||
features: Readonly<FeatureSchema[]>
|
|
||||||
): FeatureSchema[] => {
|
|
||||||
return [...features].sort((a, b) => {
|
|
||||||
const now = new Date();
|
|
||||||
const dateA = a.createdAt!;
|
|
||||||
const dateB = 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 = a.type ? diffA - expiration[a.type] : 0;
|
|
||||||
const expiredByB = b.type ? diffB - expiration[b.type] : 0;
|
|
||||||
|
|
||||||
return expiredByB - expiredByA;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortByStatus = (features: Readonly<FeatureSchema[]>): FeatureSchema[] => {
|
|
||||||
return [...features].sort((a, b) => {
|
|
||||||
if (a.stale) {
|
|
||||||
return 1;
|
|
||||||
} else if (b.stale) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user