1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: add tags filter (#5584)

This commit is contained in:
Jaanus Sellin 2023-12-11 14:10:03 +02:00 committed by GitHub
parent e8f19e6341
commit 9bae14a2cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 79 additions and 20 deletions

View File

@ -9,7 +9,7 @@ import {
} from '@mui/material';
import { Box } from '@mui/system';
import { VFC } from 'react';
import { useInstanceStats } from '../../../../hooks/api/getters/useInstanceStats/useInstanceStats';
import { useInstanceStats } from 'hooks/api/getters/useInstanceStats/useInstanceStats';
import { formatApiPath } from '../../../../utils/formatPath';
import { PageContent } from '../../../common/PageContent/PageContent';
import { PageHeader } from '../../../common/PageHeader/PageHeader';

View File

@ -1,6 +1,5 @@
import { screen } from '@testing-library/react';
import { render } from 'utils/testRenderer';
import { vi } from 'vitest';
import { FilterItemParams } from '../FilterItem/FilterItem';
import { FilterDateItem, IFilterDateItemProps } from './FilterDateItem';
@ -23,10 +22,6 @@ const setup = (initialState: FilterItemParams) => {
return recordedChanges;
};
afterEach(() => {
vi.restoreAllMocks();
});
describe('FilterDateItem Component', () => {
it('renders initial state correctly', async () => {
const mockState = {

View File

@ -9,6 +9,7 @@ import {
FilterItem,
FilterItemParams,
} from 'component/common/FilterItem/FilterItem';
import useAllTags from 'hooks/api/getters/useAllTags/useAllTags';
const StyledBox = styled(Box)(({ theme }) => ({
display: 'flex',
@ -18,6 +19,7 @@ const StyledBox = styled(Box)(({ theme }) => ({
export type FeatureTogglesListFilters = {
project?: FilterItemParams | null | undefined;
tag?: FilterItemParams | null | undefined;
state?: FilterItemParams | null | undefined;
segment?: FilterItemParams | null | undefined;
createdAt?: FilterItemParams | null | undefined;
@ -49,6 +51,7 @@ export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
}) => {
const { projects } = useProjects();
const { segments } = useSegments();
const { tags } = useAllTags();
const stateOptions = [
{
@ -81,6 +84,10 @@ export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
label: segment.name,
value: segment.name,
}));
const tagsOptions = (tags || []).map((tag) => ({
label: `${tag.type}:${tag.value}`,
value: `${tag.type}:${tag.value}`,
}));
const availableFilters: IFilterItem[] = [
{
@ -97,6 +104,18 @@ export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
singularOperators: ['IS', 'IS_NOT'],
pluralOperators: ['IS_ANY_OF', 'IS_NONE_OF'],
},
{
label: 'Tags',
options: tagsOptions,
filterKey: 'tag',
singularOperators: ['INCLUDE', 'DO_NOT_INCLUDE'],
pluralOperators: [
'INCLUDE_ALL_OF',
'INCLUDE_ANY_OF',
'EXCLUDE_IF_ANY_OF',
'EXCLUDE_ALL',
],
},
{
label: 'Segment',
options: segmentsOptions,
@ -112,12 +131,17 @@ export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
];
setAvailableFilters(availableFilters);
}, [JSON.stringify(projects), JSON.stringify(segments)]);
}, [
JSON.stringify(projects),
JSON.stringify(segments),
JSON.stringify(tags),
]);
useEffect(() => {
const filterVisibility: IFilterVisibility = {
State: Boolean(state.state),
Project: Boolean(state.project),
Tags: Boolean(state.tag),
Segment: Boolean(state.segment),
'Created date': Boolean(state.createdAt),
};
@ -127,7 +151,6 @@ export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
const hasAvailableFilters = Object.values(visibleFilters).some(
(value) => !value,
);
return (
<StyledBox>
{availableFilters.map(

View File

@ -83,6 +83,7 @@ export const FeatureToggleListTable: VFC = () => {
sortBy: withDefault(StringParam, 'createdAt'),
sortOrder: withDefault(StringParam, 'desc'),
project: FilterItemParam,
tag: FilterItemParam,
state: FilterItemParam,
segment: FilterItemParam,
createdAt: FilterItemParam,

View File

@ -4,9 +4,9 @@ import { IFeatureEnvironment } from 'interfaces/featureToggle';
import { EnvironmentVariantsTable } from './EnvironmentVariantsTable/EnvironmentVariantsTable';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Badge } from 'component/common/Badge/Badge';
import { useRequiredPathParam } from '../../../../../../hooks/useRequiredPathParam';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useVariantsFromScheduledRequests } from './useVariantsFromScheduledRequests';
import { ChangesScheduledBadge } from '../../../../../changeRequest/ModifiedInChangeRequestStatusBadge/ChangesScheduledBadge';
import { ChangesScheduledBadge } from 'component/changeRequest/ModifiedInChangeRequestStatusBadge/ChangesScheduledBadge';
import { Box } from '@mui/system';
const StyledCard = styled('div')(({ theme }) => ({

View File

@ -35,7 +35,7 @@ import { useThemeMode } from 'hooks/useThemeMode';
import { Notifications } from 'component/common/Notifications/Notifications';
import { useAdminRoutes } from 'component/admin/useAdminRoutes';
import InviteLinkButton from './InviteLink/InviteLinkButton/InviteLinkButton';
import { useUiFlag } from '../../../hooks/useUiFlag';
import { useUiFlag } from 'hooks/useUiFlag';
const StyledHeader = styled(AppBar)(({ theme }) => ({
backgroundColor: theme.palette.background.paper,

View File

@ -22,7 +22,7 @@ import { AdvancedPlaygroundResultsTable } from './AdvancedPlaygroundResultsTable
import { AdvancedPlaygroundResponseSchema } from 'openapi';
import { createLocalStorage } from 'utils/createLocalStorage';
import { BadRequestError } from 'utils/apiUtils';
import { usePlausibleTracker } from '../../../hooks/usePlausibleTracker';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
const StyledAlert = styled(Alert)(({ theme }) => ({
marginBottom: theme.spacing(3),

View File

@ -5,7 +5,6 @@ import {
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PlaygroundFeatureSchema, PlaygroundRequestSchema } from 'openapi';
import { Alert } from '@mui/material';
import { useUiFlag } from '../../../../../../hooks/useUiFlag';
interface PlaygroundResultFeatureStrategyListProps {
feature: PlaygroundFeatureSchema;

View File

@ -8,7 +8,6 @@ import {
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { FeatureStrategyItem } from './StrategyItem/FeatureStrategyItem';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
import { useUiFlag } from '../../../../../../../hooks/useUiFlag';
const StyledAlertWrapper = styled('div')(({ theme }) => ({
display: 'flex',

View File

@ -0,0 +1,37 @@
import useSWR, { mutate, SWRConfiguration } from 'swr';
import { useState, useEffect } from 'react';
import { formatApiPath } from 'utils/formatPath';
import { ITag } from 'interfaces/tags';
import handleErrorResponses from '../httpErrorResponseHandler';
const useAllTags = (options: SWRConfiguration = {}) => {
const fetcher = async () => {
const path = formatApiPath(`api/admin/tags`);
const res = await fetch(path, {
method: 'GET',
}).then(handleErrorResponses('Tags'));
return res.json();
};
const KEY = `api/admin/tags`;
const { data, error } = useSWR<{ tags: ITag[] }>(KEY, fetcher, options);
const [loading, setLoading] = useState(!error && !data);
const refetch = () => {
mutate(KEY);
};
useEffect(() => {
setLoading(!error && !data);
}, [data, error]);
return {
tags: (data?.tags as ITag[]) || [],
error,
loading,
refetch,
};
};
export default useAllTags;

View File

@ -49,13 +49,18 @@ const decodeFilterItem = (
return undefined;
}
const [operator, values = ''] = input.split(':');
if (!operator) return undefined;
const pattern =
/^(IS|IS_NOT|IS_ANY_OF|IS_NONE_OF|INCLUDE|DO_NOT_INCLUDE|INCLUDE_ALL_OF|INCLUDE_ANY_OF|EXCLUDE_IF_ANY_OF|EXCLUDE_ALL|IS_BEFORE|IS_ON_OR_AFTER):(.+)$/;
const match = input.match(pattern);
const splitValues = values.split(',');
return splitValues.length > 0
? { operator, values: splitValues }
: undefined;
if (match) {
return {
operator: match[1],
values: match[2].split(','),
};
}
return undefined;
};
export const FilterItemParam = {