diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleArchiveList.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleArchiveList.tsx deleted file mode 100644 index c0bf892afe..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleArchiveList.tsx +++ /dev/null @@ -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>; - sort: IFeaturesSort; - setSort: Dispatch>; - 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 = ({ - 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 => ( - - )); - } - - return ( - 0} - show={features.map(feature => ( - - ))} - elseShow={ - - No archived features. - - } - /> - ); - }; - - const searchResultsHeader = filter.query - ? ` (${features.length} matches)` - : ''; - - const headerTitle = isArchive - ? inProject - ? `Project Archived Features ${searchResultsHeader}` - : `Archived Features ${searchResultsHeader}` - : `Features ${searchResultsHeader}`; - - return ( -
-
- - Archive} - /> -
- - - - } - /> - - } - /> -
- } - /> - } - > - {renderFeatures()} - - - ); -}; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.tsx deleted file mode 100644 index 0ee0f1511f..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/FeatureToggleListActions.tsx +++ /dev/null @@ -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>; - sort: IFeaturesSort; - setSort: Dispatch>; - loading?: boolean; - inProject?: boolean; -} - -export const FeatureToggleListActions: VFC = ({ - 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 => ( - - {option.name} - - )); - - return ( -
- - Sorted by: - - - - setFilter(prev => ({ ...prev, project })) - } - style={{ - textTransform: 'lowercase', - fontWeight: 'normal', - }} - data-loading - /> - } - /> -
- ); -}; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/styles.ts b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/styles.ts deleted file mode 100644 index c61a280123..0000000000 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListActions/styles.ts +++ /dev/null @@ -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', - }, -}); diff --git a/frontend/src/hooks/useFeaturesSort.ts b/frontend/src/hooks/useFeaturesSort.ts deleted file mode 100644 index 0758a02b68..0000000000 --- a/frontend/src/hooks/useFeaturesSort.ts +++ /dev/null @@ -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>; -} - -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( - `${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[] => { - return [...features].sort((a, b) => - a.enabled === b.enabled ? 0 : a.enabled ? -1 : 1 - ); -}; - -const sortByStale = (features: Readonly): FeatureSchema[] => { - return [...features].sort((a, b) => - a.stale === b.stale ? 0 : a.stale ? -1 : 1 - ); -}; - -const sortByLastSeen = ( - features: Readonly -): 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[] => { - return [...features].sort((a, b) => - compareNullableDates(b.createdAt, a.createdAt) - ); -}; - -const sortByArchived = ( - features: Readonly -): FeatureSchema[] => { - return [...features].sort((a, b) => - compareNullableDates(b.archivedAt, a.archivedAt) - ); -}; - -const sortByName = (features: Readonly): FeatureSchema[] => { - return [...features].sort((a, b) => a.name.localeCompare(b.name)); -}; - -const sortByProject = ( - features: Readonly -): 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[] => { - 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[] => { - 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; - 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[] => { - return [...features].sort((a, b) => { - if (a.stale) { - return 1; - } else if (b.stale) { - return -1; - } else { - return 0; - } - }); -};