[Cases] Activate Status & Severity Filters on Load (#172514)

Closes https://github.com/elastic/kibana/issues/172458

---------

Co-authored-by: Antonio <antoniodcoelho@gmail.com>
This commit is contained in:
Julian Gernun 2023-12-05 15:54:20 +01:00 committed by GitHub
parent b0219f99d4
commit a5e0b66771
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 101 additions and 15 deletions

View file

@ -11,6 +11,7 @@ import { createAppMockRenderer } from '../../../common/mock';
import type { FilterConfig, FilterConfigRenderParams } from './types';
import { getCaseConfigure } from '../../../containers/configure/api';
import { useFilterConfig } from './use_filter_config';
import type { FilterOptions } from '../../../../common/ui';
jest.mock('../../../containers/configure/api', () => {
const originalModule = jest.requireActual('../../../containers/configure/api');
@ -20,6 +21,18 @@ jest.mock('../../../containers/configure/api', () => {
};
});
const emptyFilterOptions: FilterOptions = {
search: '',
searchFields: [],
severity: [],
status: [],
tags: [],
assignees: null,
reporters: [],
owner: [],
category: [],
customFields: {},
};
const getCaseConfigureMock = getCaseConfigure as jest.Mock;
describe('useFilterConfig', () => {
@ -67,11 +80,17 @@ describe('useFilterConfig', () => {
systemFilterConfig: filters,
onFilterOptionsChange,
isSelectorView: false,
filterOptions: emptyFilterOptions,
},
});
expect(onFilterOptionsChange).not.toHaveBeenCalled();
rerender({ systemFilterConfig: [], onFilterOptionsChange, isSelectorView: false });
rerender({
systemFilterConfig: [],
onFilterOptionsChange,
isSelectorView: false,
filterOptions: emptyFilterOptions,
});
expect(getEmptyOptions).toHaveBeenCalledTimes(1);
expect(onFilterOptionsChange).toHaveBeenCalledTimes(1);
expect(onFilterOptionsChange).toHaveBeenCalledWith({

View file

@ -5,9 +5,10 @@
* 2.0.
*/
import type { SetStateAction } from 'react';
import { useEffect, useState } from 'react';
import useLocalStorage from 'react-use/lib/useLocalStorage';
import { merge } from 'lodash';
import { merge, isEqual, isEmpty } from 'lodash';
import type { FilterOptions } from '../../../../common/ui';
import { LOCAL_STORAGE_KEYS } from '../../../../common/constants';
import type { FilterConfig, FilterConfigState } from './types';
@ -30,16 +31,61 @@ const mergeSystemAndCustomFieldConfigs = ({
return newFilterConfig;
};
const shouldBeActive = ({
filter,
filterOptions,
}: {
filter: FilterConfigState;
filterOptions: FilterOptions;
}) => {
return !filter.isActive && !isEmpty(filterOptions[filter.key as keyof FilterOptions]);
};
const useActiveByFilterKeyState = ({ filterOptions }: { filterOptions: FilterOptions }) => {
const { appId } = useCasesContext();
const [activeByFilterKey, setActiveByFilterKey] = useLocalStorage<FilterConfigState[]>(
`${appId}.${LOCAL_STORAGE_KEYS.casesTableFiltersConfig}`,
[]
);
/**
* Activates filters that aren't active but have a value in the filterOptions
*/
const newActiveByFilterKey = [...(activeByFilterKey || [])];
newActiveByFilterKey.forEach((filter) => {
if (shouldBeActive({ filter, filterOptions })) {
const currentIndex = newActiveByFilterKey.findIndex((_filter) => filter.key === _filter.key);
newActiveByFilterKey.splice(currentIndex, 1);
newActiveByFilterKey.push({ key: filter.key, isActive: true });
}
});
if (!isEqual(newActiveByFilterKey, activeByFilterKey)) {
setActiveByFilterKey(newActiveByFilterKey);
}
return [newActiveByFilterKey, setActiveByFilterKey] as [
FilterConfigState[],
(value: SetStateAction<FilterConfigState[]>) => void
];
};
export const useFilterConfig = ({
isSelectorView,
onFilterOptionsChange,
systemFilterConfig,
filterOptions,
}: {
isSelectorView: boolean;
onFilterOptionsChange: (params: Partial<FilterOptions>) => void;
systemFilterConfig: FilterConfig[];
filterOptions: FilterOptions;
}) => {
const { appId } = useCasesContext();
/**
* Initially we won't save any order, it will use the default config as it is defined in the system.
* Once the user adds/removes a filter, we start saving the order and the visible state.
*/
const [activeByFilterKey, setActiveByFilterKey] = useActiveByFilterKeyState({ filterOptions });
const { customFieldsFilterConfig } = useCustomFieldsFilterConfig({
isSelectorView,
onFilterOptionsChange,
@ -47,14 +93,6 @@ export const useFilterConfig = ({
const [filterConfigs, setFilterConfigs] = useState<Map<string, FilterConfig>>(
() => new Map([...systemFilterConfig].map((filter) => [filter.key, filter]))
);
/**
* Initially we won't save any order, it will use the default config as it is defined in the system.
* Once the user adds/removes a filter, we start saving the order and the visible state.
*/
const [activeByFilterKey, setActiveByFilterKey] = useLocalStorage<FilterConfigState[]>(
`${appId}.${LOCAL_STORAGE_KEYS.casesTableFiltersConfig}`,
[]
);
/**
* This effect is needed in case a filter (mostly custom field) is removed from the settings

View file

@ -12,7 +12,7 @@ import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl';
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
import { waitForComponentToUpdate } from '../../common/test_utils';
import { CaseStatuses, CustomFieldTypes } from '../../../common/types/domain';
import { CaseStatuses, CustomFieldTypes, CaseSeverity } from '../../../common/types/domain';
import { SECURITY_SOLUTION_OWNER, OBSERVABILITY_OWNER } from '../../../common/constants';
import type { AppMockRenderer } from '../../common/mock';
import { createAppMockRenderer } from '../../common/mock';
@ -857,4 +857,34 @@ describe('CasesTableFilters ', () => {
`);
});
});
it('should activate a filter when there is a value in the global state as this means that it has a value set in the url', async () => {
const previousState = [
{ key: 'severity', isActive: false }, // notice severity filter not active
{ key: 'status', isActive: false }, // notice status filter not active
{ key: 'tags', isActive: true },
{ key: 'category', isActive: false },
];
localStorage.setItem('testAppId.cases.list.tableFiltersConfig', JSON.stringify(previousState));
const overrideProps = {
...props,
filterOptions: {
...DEFAULT_FILTER_OPTIONS,
severity: [CaseSeverity.MEDIUM], // but they have values
status: [CaseStatuses.open, CaseStatuses['in-progress']],
},
};
appMockRender.render(<CasesTableFilters {...overrideProps} />);
const statusButton = await screen.findByRole('button', { name: 'Status' });
expect(statusButton).toBeInTheDocument();
expect(within(statusButton).getByLabelText('2 active filters')).toBeInTheDocument();
const severityButton = await screen.findByRole('button', { name: 'Severity' });
expect(severityButton).toBeInTheDocument();
expect(within(severityButton).getByLabelText('1 active filters')).toBeInTheDocument();
});
});

View file

@ -6,9 +6,8 @@
*/
import React, { useCallback, useState } from 'react';
import { isEqual } from 'lodash/fp';
import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiFilterGroup, EuiButton } from '@elastic/eui';
import { mergeWith } from 'lodash';
import { mergeWith, isEqual } from 'lodash';
import { MoreFiltersSelectable } from './table_filter_config/more_filters_selectable';
import type { CaseStatuses } from '../../../common/types/domain';
import type { FilterOptions } from '../../containers/types';
@ -107,7 +106,7 @@ const CasesTableFiltersComponent = ({
selectableOptions,
activeSelectableOptionKeys,
onFilterConfigChange,
} = useFilterConfig({ systemFilterConfig, onFilterOptionsChange, isSelectorView });
} = useFilterConfig({ systemFilterConfig, onFilterOptionsChange, isSelectorView, filterOptions });
const handleOnSearch = useCallback(
(newSearch) => {