1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01: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 { 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: [],
},
]);
});
});

View File

@ -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<IFilterItemProps> = ({
onChange,
onChipClose,
state,
singularOperators,
pluralOperators,
}) => {
const ref = useRef<HTMLDivElement>(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 { 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<IFeatureToggleFiltersProps> = ({
onChange,
}) => {
const { projects } = useProjects();
const { segments } = useSegments();
const stateOptions = [
{
@ -66,6 +69,10 @@ export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
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<IFeatureToggleFiltersProps> = ({
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 (
<StyledBox>
@ -98,6 +114,8 @@ export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
onChange={(value) =>
onChange({ [filter.filterKey]: value })
}
singularOperators={['IS', 'IS_NOT']}
pluralOperators={['IS_ANY_OF', 'IS_NONE_OF']}
onChipClose={() => removeFilter(filter.label)}
/>
),

View File

@ -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',