1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-10 01:16:39 +02:00

feat: segments filter (#5558)

Co-authored-by: sjaanus <sellinjaanus@gmail.com>
This commit is contained in:
Mateusz Kwasniewski 2023-12-06 13:50:18 +01:00 committed by GitHub
parent eda4186a6c
commit 87ebbb0fa2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 66 additions and 20 deletions

View File

@ -1,23 +1,34 @@
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { render } from 'utils/testRenderer'; import { render } from 'utils/testRenderer';
import { FilterItem } from './FilterItem'; import { FilterItem, IFilterItemProps } from './FilterItem';
const getOption = (option: string) => const getOption = (option: string) =>
screen.getByText(option).closest('li')!.querySelector('input')!; screen.getByText(option).closest('li')!.querySelector('input')!;
const setup = (initialState: FilterItem) => { const setup = (initialState: FilterItem) => {
const recordedChanges: FilterItem[] = []; const recordedChanges: FilterItem[] = [];
const mockProps = { const mockProps: IFilterItemProps = {
label: 'Test Label', label: 'Test Label',
options: [ options: [
{ label: 'Option 1', value: '1' }, {
{ label: 'Option 2', value: '2' }, label: 'Option 1',
{ label: 'Option 3', value: '3' }, value: '1',
},
{
label: 'Option 2',
value: '2',
},
{
label: 'Option 3',
value: '3',
},
], ],
onChange: (value: FilterItem) => { onChange: (value: FilterItem) => {
recordedChanges.push(value); recordedChanges.push(value);
}, },
onChipClose: () => {}, onChipClose: () => {},
singularOperators: ['IS', 'IS_NOT'],
pluralOperators: ['IS_ANY_OF', 'IS_NONE_OF'],
state: initialState, state: initialState,
}; };
@ -49,7 +60,10 @@ describe('FilterItem Component', () => {
getOption('Option 2').click(); getOption('Option 2').click();
expect(recordedChanges).toEqual([ 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); 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 () => { it('adjusts operator to match plural items', async () => {
@ -73,7 +92,10 @@ describe('FilterItem Component', () => {
const recordedChanges = setup(mockState); const recordedChanges = setup(mockState);
expect(recordedChanges).toEqual([ 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(); newOperator.click();
expect(recordedChanges).toEqual([ 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(); deleteElement.click();
expect(recordedChanges).toEqual([{ operator: 'IS', values: [] }]); expect(recordedChanges).toEqual([
{
operator: 'IS',
values: [],
},
]);
}); });
}); });

View File

@ -1,5 +1,5 @@
import { Search } from '@mui/icons-material'; 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 { FC, useEffect, useRef, useState } from 'react';
import { import {
StyledCheckbox, StyledCheckbox,
@ -9,22 +9,17 @@ import {
StyledTextField, StyledTextField,
} from './FilterItem.styles'; } from './FilterItem.styles';
import { FilterItemChip } from './FilterItemChip/FilterItemChip'; import { FilterItemChip } from './FilterItemChip/FilterItemChip';
import {
FeatureTogglesListFilters,
IFilterItem,
} from '../../feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters';
interface IFilterItemProps { export interface IFilterItemProps {
label: string; label: string;
options: Array<{ label: string; value: string }>; options: Array<{ label: string; value: string }>;
onChange: (value: FilterItem) => void; onChange: (value: FilterItem) => void;
onChipClose: () => void; onChipClose: () => void;
state: FilterItem | null | undefined; 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 = { export type FilterItem = {
operator: string; operator: string;
values: string[]; values: string[];
@ -36,6 +31,8 @@ export const FilterItem: FC<IFilterItemProps> = ({
onChange, onChange,
onChipClose, onChipClose,
state, state,
singularOperators,
pluralOperators,
}) => { }) => {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [anchorEl, setAnchorEl] = useState<HTMLDivElement | null>(null); const [anchorEl, setAnchorEl] = useState<HTMLDivElement | null>(null);

View File

@ -4,6 +4,7 @@ import { FilterItem } from 'component/common/FilterItem/FilterItem';
import useProjects from 'hooks/api/getters/useProjects/useProjects'; import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import AddFilterButton from './AddFilterButton'; import AddFilterButton from './AddFilterButton';
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
const StyledBox = styled(Box)(({ theme }) => ({ const StyledBox = styled(Box)(({ theme }) => ({
display: 'flex', display: 'flex',
@ -14,6 +15,7 @@ const StyledBox = styled(Box)(({ theme }) => ({
export type FeatureTogglesListFilters = { export type FeatureTogglesListFilters = {
project?: FilterItem | null | undefined; project?: FilterItem | null | undefined;
state?: FilterItem | null | undefined; state?: FilterItem | null | undefined;
segment?: FilterItem | null | undefined;
}; };
interface IFeatureToggleFiltersProps { interface IFeatureToggleFiltersProps {
@ -36,6 +38,7 @@ export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
onChange, onChange,
}) => { }) => {
const { projects } = useProjects(); const { projects } = useProjects();
const { segments } = useSegments();
const stateOptions = [ const stateOptions = [
{ {
@ -66,6 +69,10 @@ export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
label: project.name, label: project.name,
value: project.id, value: project.id,
})); }));
const segmentsOptions = (segments || []).map((segment) => ({
label: segment.name,
value: segment.name,
}));
const newFilterItems: IFilterItem[] = [ const newFilterItems: IFilterItem[] = [
{ {
@ -80,10 +87,19 @@ export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
filterKey: 'project', filterKey: 'project',
enabled: Boolean(state.project), enabled: Boolean(state.project),
} as const, } as const,
{
label: 'Segment',
options: segmentsOptions,
filterKey: 'segment',
} as const,
]; ];
setAvailableFilters(newFilterItems); setAvailableFilters(newFilterItems);
}, [JSON.stringify(projects), JSON.stringify(state)]); }, [
JSON.stringify(projects),
JSON.stringify(state),
JSON.stringify(segments),
]);
return ( return (
<StyledBox> <StyledBox>
@ -98,6 +114,8 @@ export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
onChange={(value) => onChange={(value) =>
onChange({ [filter.filterKey]: value }) onChange({ [filter.filterKey]: value })
} }
singularOperators={['IS', 'IS_NOT']}
pluralOperators={['IS_ANY_OF', 'IS_NONE_OF']}
onChipClose={() => removeFilter(filter.label)} onChipClose={() => removeFilter(filter.label)}
/> />
), ),

View File

@ -84,6 +84,7 @@ export const FeatureToggleListTable: VFC = () => {
sortOrder: withDefault(StringParam, 'desc'), sortOrder: withDefault(StringParam, 'desc'),
project: FilterItemParam, project: FilterItemParam,
state: FilterItemParam, state: FilterItemParam,
segment: FilterItemParam,
}; };
const [tableState, setTableState] = usePersistentTableState( const [tableState, setTableState] = usePersistentTableState(
'features-list-table', 'features-list-table',