diff --git a/frontend/src/component/common/FilterItem/FilterItemChip/FilterItemChip.tsx b/frontend/src/component/common/FilterItem/FilterItemChip/FilterItemChip.tsx index 714130996b..ffad617782 100644 --- a/frontend/src/component/common/FilterItem/FilterItemChip/FilterItemChip.tsx +++ b/frontend/src/component/common/FilterItem/FilterItemChip/FilterItemChip.tsx @@ -4,13 +4,14 @@ import { ArrowDropDown, Close, TopicOutlined } from '@mui/icons-material'; import { ConditionallyRender } from '../../ConditionallyRender/ConditionallyRender'; import { Chip, IconButton, styled } from '@mui/material'; import { FilterItemOperator } from './FilterItemOperator/FilterItemOperator'; +import { FILTER_ITEM } from 'utils/testIds'; const StyledChip = styled( ({ isActive, ...props }: { isActive: boolean } & ComponentProps) => ( - + ), )(({ theme, isActive = false }) => ({ borderRadius: `${theme.shape.borderRadius}px`, diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/AddFilterButton/AddFilterButton.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/AddFilterButton/AddFilterButton.tsx index 6d39a38e8f..739d371929 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/AddFilterButton/AddFilterButton.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/AddFilterButton/AddFilterButton.tsx @@ -2,22 +2,26 @@ import React, { useState } from 'react'; import Button from '@mui/material/Button'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; -import { IFilterVisibility, IFilterItem } from '../FeatureToggleFilters'; -import { Box, styled } from '@mui/material'; +import { styled } from '@mui/material'; import { Add } from '@mui/icons-material'; const StyledButton = styled(Button)(({ theme }) => ({ margin: theme.spacing(-1, 0, -1, 0), padding: theme.spacing(1.25), })); + interface IAddFilterButtonProps { - visibleFilters: IFilterVisibility; - setVisibleFilters: (filters: IFilterVisibility) => void; + visibleOptions: string[]; + setVisibleOptions: (filters: string[]) => void; + hiddenOptions: string[]; + setHiddenOptions: (filters: string[]) => void; } const AddFilterButton = ({ - visibleFilters, - setVisibleFilters, + visibleOptions, + setVisibleOptions, + hiddenOptions, + setHiddenOptions, }: IAddFilterButtonProps) => { const [anchorEl, setAnchorEl] = useState(null); @@ -28,12 +32,12 @@ const AddFilterButton = ({ setAnchorEl(null); }; - const onClick = (label: string) => { - const filterVisibility = { - ...visibleFilters, - [label]: true, - }; - setVisibleFilters(filterVisibility); + const onSelect = (label: string) => { + const newVisibleOptions = visibleOptions.filter((f) => f !== label); + const newHiddenOptions = [...hiddenOptions, label]; + + setHiddenOptions(newHiddenOptions); + setVisibleOptions(newVisibleOptions); handleClose(); }; @@ -49,13 +53,11 @@ const AddFilterButton = ({ open={Boolean(anchorEl)} onClose={handleClose} > - {Object.entries(visibleFilters).map(([label, enabled]) => - !enabled ? ( - onClick(label)}> - {label} - - ) : null, - )} + {visibleOptions.map((label) => ( + onSelect(label)}> + {label} + + ))} ); diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters.test.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters.test.tsx index f10ac80ede..c50f618e63 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters.test.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters.test.tsx @@ -2,6 +2,7 @@ import { screen } from '@testing-library/react'; import { render } from 'utils/testRenderer'; import { testServerRoute, testServerSetup } from 'utils/testServer'; import { FeatureToggleFilters } from './FeatureToggleFilters'; +import { FILTER_ITEM } from 'utils/testIds'; const server = testServerSetup(); @@ -38,3 +39,21 @@ test('should not render projects filters when less than two project', async () = expect(screen.queryByText('Projects')).not.toBeInTheDocument(); }); + +test('should keep filters order when adding a new filter', async () => { + render( {}} state={{}} />); + + const valuesElement = await screen.findByText('Tags'); + expect(valuesElement).toBeInTheDocument(); + valuesElement.click(); + + const stateElement = await screen.findByText('State'); + expect(stateElement).toBeInTheDocument(); + + stateElement.click(); + + const filterItems = screen.getAllByTestId(FILTER_ITEM); + const filterTexts = filterItems.map((item) => item.textContent); + + expect(filterTexts).toEqual(['Tags', 'State']); +}); diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters.tsx index fb2c09c4bc..cbc09b469d 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters.tsx @@ -10,6 +10,7 @@ import { FilterItemParams, } from 'component/common/FilterItem/FilterItem'; import useAllTags from 'hooks/api/getters/useAllTags/useAllTags'; +import { FILTER_ITEM } from 'utils/testIds'; const StyledBox = styled(Box)(({ theme }) => ({ display: 'flex', @@ -42,10 +43,6 @@ export interface IFilterItem { pluralOperators: [string, ...string[]]; } -export type IFilterVisibility = { - [key: string]: boolean | undefined; -}; - export const FeatureToggleFilters: VFC = ({ state, onChange, @@ -66,14 +63,30 @@ export const FeatureToggleFilters: VFC = ({ ]; const [availableFilters, setAvailableFilters] = useState([]); - const [visibleFilters, setVisibleFilters] = useState({}); + const [unselectedFilters, setUnselectedFilters] = useState([]); + const [selectedFilters, setSelectedFilters] = useState([]); - const hideFilter = (label: string) => { - const filterVisibility = { - ...visibleFilters, - [label]: false, - }; - setVisibleFilters(filterVisibility); + const deselectFilter = (label: string) => { + const newSelectedFilters = selectedFilters.filter((f) => f !== label); + const newUnselectedFilters = [...unselectedFilters, label].sort(); + + setSelectedFilters(newSelectedFilters); + setUnselectedFilters(newUnselectedFilters); + }; + + const mergeArraysKeepingOrder = ( + firstArray: string[], + secondArray: string[], + ): string[] => { + const elementsSet = new Set(firstArray); + + secondArray.forEach((element) => { + if (!elementsSet.has(element)) { + firstArray.push(element); + } + }); + + return firstArray; }; useEffect(() => { @@ -147,59 +160,102 @@ export const FeatureToggleFilters: VFC = ({ useEffect(() => { const hasMultipleProjects = projects.length > 1; - const filterVisibility: IFilterVisibility = { - State: Boolean(state.state), - ...(hasMultipleProjects && { - Project: Boolean(state.project), - }), - Tags: Boolean(state.tag), - Segment: Boolean(state.segment), - 'Created date': Boolean(state.createdAt), - }; - setVisibleFilters(filterVisibility); + const fieldsMapping = [ + { + stateField: 'state', + label: 'State', + }, + ...(hasMultipleProjects + ? [ + { + stateField: 'project', + label: 'Project', + }, + ] + : []), + { + stateField: 'tag', + label: 'Tags', + }, + { + stateField: 'segment', + label: 'Segment', + }, + { + stateField: 'createdAt', + label: 'Created date', + }, + ]; + + const newSelectedFilters = fieldsMapping + .filter((field) => + Boolean( + state[field.stateField as keyof FeatureTogglesListFilters], + ), + ) + .map((field) => field.label); + const newUnselectedFilters = fieldsMapping + .filter( + (field) => + !state[field.stateField as keyof FeatureTogglesListFilters], + ) + .map((field) => field.label) + .sort(); + + setSelectedFilters( + mergeArraysKeepingOrder(selectedFilters, newSelectedFilters), + ); + setUnselectedFilters(newUnselectedFilters); }, [JSON.stringify(state), JSON.stringify(projects)]); - const hasAvailableFilters = Object.values(visibleFilters).some( - (value) => !value, - ); + const hasAvailableFilters = unselectedFilters.length > 0; return ( - {availableFilters.map( - (filter) => - visibleFilters[filter.label] && ( - - onChange({ [filter.filterKey]: value }) - } - singularOperators={filter.singularOperators} - pluralOperators={filter.pluralOperators} - onChipClose={() => hideFilter(filter.label)} + {selectedFilters.map((selectedFilter) => { + if (selectedFilter === 'Created date') { + return ( + onChange({ createdAt: value })} + operators={['IS_ON_OR_AFTER', 'IS_BEFORE']} + onChipClose={() => deselectFilter('Created date')} /> - ), - )} - onChange({ createdAt: value })} - operators={['IS_ON_OR_AFTER', 'IS_BEFORE']} - onChipClose={() => hideFilter('Created date')} - /> + ); } - /> + + const filter = availableFilters.find( + (filter) => filter.label === selectedFilter, + ); + + if (!filter) { + return null; + } + + return ( + + onChange({ [filter.filterKey]: value }) + } + singularOperators={filter.singularOperators} + pluralOperators={filter.pluralOperators} + onChipClose={() => deselectFilter(filter.label)} + /> + ); + })} } /> diff --git a/frontend/src/utils/testIds.ts b/frontend/src/utils/testIds.ts index ce6dbd6460..3dd99c8398 100644 --- a/frontend/src/utils/testIds.ts +++ b/frontend/src/utils/testIds.ts @@ -98,3 +98,5 @@ export const BATCH_ACTIONS_BAR = 'BATCH_ACTIONS_BAR'; export const BATCH_SELECTED_COUNT = 'BATCH_SELECTED_COUNT'; export const BATCH_SELECT = 'BATCH_SELECT'; export const MORE_BATCH_ACTIONS = 'MORE_BATCH_ACTIONS'; + +export const FILTER_ITEM = 'FILTER_ITEM';