From 87ebbb0fa2d151a536534fa5064a8b3b160c2395 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Wed, 6 Dec 2023 13:50:18 +0100 Subject: [PATCH] feat: segments filter (#5558) Co-authored-by: sjaanus --- .../common/FilterItem/FilterItem.test.tsx | 50 +++++++++++++++---- .../common/FilterItem/FilterItem.tsx | 15 +++--- .../FeatureToggleFilters.tsx | 20 +++++++- .../FeatureToggleListTable.tsx | 1 + 4 files changed, 66 insertions(+), 20 deletions(-) diff --git a/frontend/src/component/common/FilterItem/FilterItem.test.tsx b/frontend/src/component/common/FilterItem/FilterItem.test.tsx index 8cc55e993c..d44392002b 100644 --- a/frontend/src/component/common/FilterItem/FilterItem.test.tsx +++ b/frontend/src/component/common/FilterItem/FilterItem.test.tsx @@ -1,23 +1,34 @@ import { screen } from '@testing-library/react'; import { render } from 'utils/testRenderer'; -import { FilterItem } from './FilterItem'; +import { FilterItem, IFilterItemProps } from './FilterItem'; const getOption = (option: string) => screen.getByText(option).closest('li')!.querySelector('input')!; const setup = (initialState: FilterItem) => { const recordedChanges: FilterItem[] = []; - const mockProps = { + const mockProps: IFilterItemProps = { label: 'Test Label', options: [ - { label: 'Option 1', value: '1' }, - { label: 'Option 2', value: '2' }, - { label: 'Option 3', value: '3' }, + { + label: 'Option 1', + value: '1', + }, + { + label: 'Option 2', + value: '2', + }, + { + label: 'Option 3', + value: '3', + }, ], onChange: (value: FilterItem) => { recordedChanges.push(value); }, onChipClose: () => {}, + singularOperators: ['IS', 'IS_NOT'], + pluralOperators: ['IS_ANY_OF', 'IS_NONE_OF'], state: initialState, }; @@ -49,7 +60,10 @@ describe('FilterItem Component', () => { getOption('Option 2').click(); expect(recordedChanges).toEqual([ - { operator: 'IS_ANY_OF', values: ['1', '3', '2'] }, + { + operator: 'IS_ANY_OF', + values: ['1', '3', '2'], + }, ]); }); @@ -61,7 +75,12 @@ describe('FilterItem Component', () => { const recordedChanges = setup(mockState); - expect(recordedChanges).toEqual([{ operator: 'IS', values: ['1'] }]); + expect(recordedChanges).toEqual([ + { + operator: 'IS', + values: ['1'], + }, + ]); }); it('adjusts operator to match plural items', async () => { @@ -73,7 +92,10 @@ describe('FilterItem Component', () => { const recordedChanges = setup(mockState); expect(recordedChanges).toEqual([ - { operator: 'IS_ANY_OF', values: ['1', '2'] }, + { + operator: 'IS_ANY_OF', + values: ['1', '2'], + }, ]); }); @@ -93,7 +115,10 @@ describe('FilterItem Component', () => { newOperator.click(); expect(recordedChanges).toEqual([ - { operator: 'IS_NONE_OF', values: ['1', '3'] }, + { + operator: 'IS_NONE_OF', + values: ['1', '3'], + }, ]); }); @@ -109,6 +134,11 @@ describe('FilterItem Component', () => { deleteElement.click(); - expect(recordedChanges).toEqual([{ operator: 'IS', values: [] }]); + expect(recordedChanges).toEqual([ + { + operator: 'IS', + values: [], + }, + ]); }); }); diff --git a/frontend/src/component/common/FilterItem/FilterItem.tsx b/frontend/src/component/common/FilterItem/FilterItem.tsx index e1927d3699..c35d20ec33 100644 --- a/frontend/src/component/common/FilterItem/FilterItem.tsx +++ b/frontend/src/component/common/FilterItem/FilterItem.tsx @@ -1,5 +1,5 @@ import { Search } from '@mui/icons-material'; -import { List, ListItemText, Box, InputAdornment } from '@mui/material'; +import { Box, InputAdornment, List, ListItemText } from '@mui/material'; import { FC, useEffect, useRef, useState } from 'react'; import { StyledCheckbox, @@ -9,22 +9,17 @@ import { StyledTextField, } from './FilterItem.styles'; import { FilterItemChip } from './FilterItemChip/FilterItemChip'; -import { - FeatureTogglesListFilters, - IFilterItem, -} from '../../feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters'; -interface IFilterItemProps { +export interface IFilterItemProps { label: string; options: Array<{ label: string; value: string }>; onChange: (value: FilterItem) => void; onChipClose: () => void; state: FilterItem | null | undefined; + singularOperators: [string, ...string[]]; + pluralOperators: [string, ...string[]]; } -const singularOperators = ['IS', 'IS_NOT']; -const pluralOperators = ['IS_ANY_OF', 'IS_NONE_OF']; - export type FilterItem = { operator: string; values: string[]; @@ -36,6 +31,8 @@ export const FilterItem: FC = ({ onChange, onChipClose, state, + singularOperators, + pluralOperators, }) => { const ref = useRef(null); const [anchorEl, setAnchorEl] = useState(null); diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters.tsx index bd3eb5ac8c..2753fd6006 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters.tsx @@ -4,6 +4,7 @@ import { FilterItem } from 'component/common/FilterItem/FilterItem'; import useProjects from 'hooks/api/getters/useProjects/useProjects'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import AddFilterButton from './AddFilterButton'; +import { useSegments } from 'hooks/api/getters/useSegments/useSegments'; const StyledBox = styled(Box)(({ theme }) => ({ display: 'flex', @@ -14,6 +15,7 @@ const StyledBox = styled(Box)(({ theme }) => ({ export type FeatureTogglesListFilters = { project?: FilterItem | null | undefined; state?: FilterItem | null | undefined; + segment?: FilterItem | null | undefined; }; interface IFeatureToggleFiltersProps { @@ -36,6 +38,7 @@ export const FeatureToggleFilters: VFC = ({ onChange, }) => { const { projects } = useProjects(); + const { segments } = useSegments(); const stateOptions = [ { @@ -66,6 +69,10 @@ export const FeatureToggleFilters: VFC = ({ label: project.name, value: project.id, })); + const segmentsOptions = (segments || []).map((segment) => ({ + label: segment.name, + value: segment.name, + })); const newFilterItems: IFilterItem[] = [ { @@ -80,10 +87,19 @@ export const FeatureToggleFilters: VFC = ({ filterKey: 'project', enabled: Boolean(state.project), } as const, + { + label: 'Segment', + options: segmentsOptions, + filterKey: 'segment', + } as const, ]; setAvailableFilters(newFilterItems); - }, [JSON.stringify(projects), JSON.stringify(state)]); + }, [ + JSON.stringify(projects), + JSON.stringify(state), + JSON.stringify(segments), + ]); return ( @@ -98,6 +114,8 @@ export const FeatureToggleFilters: VFC = ({ onChange={(value) => onChange({ [filter.filterKey]: value }) } + singularOperators={['IS', 'IS_NOT']} + pluralOperators={['IS_ANY_OF', 'IS_NONE_OF']} onChipClose={() => removeFilter(filter.label)} /> ), diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx index 9e92d9cf3c..eac176ed68 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx @@ -84,6 +84,7 @@ export const FeatureToggleListTable: VFC = () => { sortOrder: withDefault(StringParam, 'desc'), project: FilterItemParam, state: FilterItemParam, + segment: FilterItemParam, }; const [tableState, setTableState] = usePersistentTableState( 'features-list-table',