Skip to content

Commit

Permalink
[Cases] Refactor Cases List Filters (#169371)
Browse files Browse the repository at this point in the history
Meta #167651

### Description
Part 2/3. This PR refactors the filters to be multi select

<img width="829" alt="Screenshot"
src="https://github.com/elastic/kibana/assets/17549662/5b0efe3f-f4a0-423e-9d64-0dfa50e74ce6">

### QA
- Filters work as multi select now
- 'All' has been removed as option for status and severity. Check that
old urls, local storage values do not break anything
- Review the cases list in the modal when adding a timeline to a case

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Antonio <antoniodcoelho@gmail.com>
  • Loading branch information
3 people authored Nov 8, 2023
1 parent b5cb887 commit 36d8eba
Show file tree
Hide file tree
Showing 43 changed files with 956 additions and 666 deletions.
1 change: 0 additions & 1 deletion x-pack/plugins/cases/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ export {
export type { AttachmentAttributes } from './types/domain';
export { ConnectorTypes, AttachmentType, ExternalReferenceStorageType } from './types/domain';
export { getCasesFromAlertsUrl, getCaseFindUserActionsUrl, throwErrors } from './api';
export { StatusAll } from './ui/types';
export { createUICapabilities, type CasesUiCapabilities } from './utils/capabilities';
export { getApiTags, type CasesApiTags } from './utils/api_tags';
export { CaseMetricsFeature } from './types/api';
Expand Down
12 changes: 2 additions & 10 deletions x-pack/plugins/cases/common/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,6 @@ export interface CasesUiConfigType {
};
}

export const StatusAll = 'all' as const;
export type StatusAllType = typeof StatusAll;

export type CaseStatusWithAllStatus = CaseStatuses | StatusAllType;

export const SeverityAll = 'all' as const;
export type CaseSeverityWithAll = CaseSeverity | typeof SeverityAll;

export const UserActionTypeAll = 'all' as const;
export type CaseUserActionTypeWithAll = UserActionFindRequestTypes | typeof UserActionTypeAll;

Expand Down Expand Up @@ -156,8 +148,8 @@ export type LocalStorageQueryParams = Partial<Omit<QueryParams, 'page'>>;
export interface FilterOptions {
search: string;
searchFields: string[];
severity: CaseSeverityWithAll[];
status: CaseStatusWithAllStatus[];
severity: CaseSeverity[];
status: CaseStatuses[];
tags: string[];
assignees: Array<string | null> | null;
reporters: User[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from '../../common/mock';
import { useGetCasesMockState, connectorsMock } from '../../containers/mock';

import { SortFieldCase, StatusAll } from '../../../common/ui/types';
import { SortFieldCase } from '../../../common/ui/types';
import { CaseSeverity, CaseStatuses } from '../../../common/types/domain';
import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { getEmptyTagValue } from '../empty_value';
Expand Down Expand Up @@ -393,7 +393,8 @@ describe('AllCasesListGeneric', () => {
it('should sort by status', async () => {
appMockRenderer.render(<AllCasesList isSelectorView={false} />);

userEvent.click(screen.getByTitle('Status'));
// 0 is the status filter button label
userEvent.click(screen.getAllByTitle('Status')[1]);

await waitFor(() => {
expect(useGetCasesMock).toHaveBeenLastCalledWith(
Expand All @@ -414,14 +415,16 @@ describe('AllCasesListGeneric', () => {
expect(screen.getByTitle('Name')).toBeInTheDocument();
expect(screen.getByTitle('Category')).toBeInTheDocument();
expect(screen.getByTitle('Created on')).toBeInTheDocument();
expect(screen.getByTitle('Severity')).toBeInTheDocument();
// 0 is the severity filter button label
expect(screen.getAllByTitle('Severity')[1]).toBeInTheDocument();
});
});

it('should sort by severity', async () => {
appMockRenderer.render(<AllCasesList isSelectorView={false} />);

userEvent.click(screen.getByTitle('Severity'));
// 0 is the severity filter button label
userEvent.click(screen.getAllByTitle('Severity')[1]);

await waitFor(() => {
expect(useGetCasesMock).toHaveBeenLastCalledWith(
Expand Down Expand Up @@ -493,7 +496,7 @@ describe('AllCasesListGeneric', () => {
it('should filter by category', async () => {
appMockRenderer.render(<AllCasesList isSelectorView={false} />);

userEvent.click(screen.getByTestId('options-filter-popover-button-Categories'));
userEvent.click(screen.getByTestId('options-filter-popover-button-category'));
await waitForEuiPopoverOpen();
userEvent.click(screen.getByTestId('options-filter-popover-item-twix'));

Expand All @@ -510,72 +513,22 @@ describe('AllCasesListGeneric', () => {
});
});

it('should filter by status: closed', async () => {
appMockRenderer.render(<AllCasesList isSelectorView={false} />);
userEvent.click(screen.getByTestId('case-status-filter'));
await waitForEuiPopoverOpen();
userEvent.click(screen.getByTestId('case-status-filter-closed'));
await waitFor(() => {
expect(useGetCasesMock).toHaveBeenLastCalledWith(
expect.objectContaining({
queryParams: { ...DEFAULT_QUERY_PARAMS, sortField: SortFieldCase.closedAt },
})
);
});
});

it('should filter by status: in-progress', async () => {
appMockRenderer.render(<AllCasesList isSelectorView={false} />);
userEvent.click(screen.getByTestId('case-status-filter'));
await waitForEuiPopoverOpen();
userEvent.click(screen.getByTestId('case-status-filter-in-progress'));
await waitFor(() => {
expect(useGetCasesMock).toHaveBeenLastCalledWith(
expect.objectContaining({
queryParams: DEFAULT_QUERY_PARAMS,
})
);
});
});

it('should filter by status: open', async () => {
appMockRenderer.render(<AllCasesList isSelectorView={false} />);
userEvent.click(screen.getByTestId('case-status-filter'));
await waitForEuiPopoverOpen();
userEvent.click(screen.getByTestId('case-status-filter-in-progress'));
await waitFor(() => {
expect(useGetCasesMock).toHaveBeenLastCalledWith(
expect.objectContaining({
queryParams: DEFAULT_QUERY_PARAMS,
})
);
});
});

it('should show the correct count on stats', async () => {
appMockRenderer.render(<AllCasesList isSelectorView={false} />);

userEvent.click(screen.getByTestId('case-status-filter'));
userEvent.click(screen.getByTestId('options-filter-popover-button-status'));

await waitFor(() => {
expect(screen.getByTestId('case-status-filter-open')).toHaveTextContent('Open (20)');
expect(screen.getByTestId('case-status-filter-in-progress')).toHaveTextContent(
expect(screen.getByTestId('options-filter-popover-item-open')).toHaveTextContent('Open (20)');
expect(screen.getByTestId('options-filter-popover-item-in-progress')).toHaveTextContent(
'In progress (40)'
);
expect(screen.getByTestId('case-status-filter-closed')).toHaveTextContent('Closed (130)');
expect(screen.getByTestId('options-filter-popover-item-closed')).toHaveTextContent(
'Closed (130)'
);
});
});

it('renders the first available status when hiddenStatus is given', async () => {
appMockRenderer.render(
<AllCasesList hiddenStatuses={[StatusAll, CaseStatuses.open]} isSelectorView={true} />
);

await waitFor(() =>
expect(screen.getAllByTestId('case-status-badge-in-progress')[0]).toBeInTheDocument()
);
});

it('shows Solution column if there are no set owners', async () => {
render(
<TestProviders owner={[]}>
Expand Down Expand Up @@ -632,9 +585,9 @@ describe('AllCasesListGeneric', () => {
expect(checkbox).toBeChecked();
}

userEvent.click(screen.getByTestId('case-status-filter'));
userEvent.click(screen.getByTestId('options-filter-popover-button-status'));
await waitForEuiPopoverOpen();
userEvent.click(screen.getByTestId('case-status-filter-closed'));
userEvent.click(screen.getByTestId('options-filter-popover-item-open'));

for (const checkbox of checkboxes) {
expect(checkbox).not.toBeChecked();
Expand Down Expand Up @@ -692,9 +645,9 @@ describe('AllCasesListGeneric', () => {
filterOptions: {
search: '',
searchFields: ['title', 'description'],
severity: ['all'],
severity: [],
reporters: [],
status: ['all'],
status: [],
tags: [],
assignees: [],
owner: ['securitySolution', 'observability'],
Expand All @@ -719,9 +672,9 @@ describe('AllCasesListGeneric', () => {
filterOptions: {
search: '',
searchFields: ['title', 'description'],
severity: ['all'],
severity: [],
reporters: [],
status: ['all'],
status: [],
tags: [],
assignees: [],
owner: ['securitySolution'],
Expand All @@ -742,9 +695,9 @@ describe('AllCasesListGeneric', () => {
filterOptions: {
search: '',
searchFields: ['title', 'description'],
severity: ['all'],
severity: [],
reporters: [],
status: ['all'],
status: [],
tags: [],
assignees: [],
owner: ['securitySolution', 'observability'],
Expand Down Expand Up @@ -775,9 +728,9 @@ describe('AllCasesListGeneric', () => {
filterOptions: {
search: '',
searchFields: ['title', 'description'],
severity: ['all'],
severity: [],
reporters: [],
status: ['all'],
status: [],
tags: [],
assignees: [],
owner: ['securitySolution'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,13 @@ import { EuiProgress } from '@elastic/eui';
import { difference, head, isEmpty } from 'lodash/fp';
import styled, { css } from 'styled-components';

import type {
CaseUI,
CaseStatusWithAllStatus,
FilterOptions,
CasesUI,
} from '../../../common/ui/types';
import type { CaseUI, FilterOptions, CasesUI } from '../../../common/ui/types';
import type { CasesOwners } from '../../client/helpers/can_use_cases';
import type { EuiBasicTableOnChange, Solution } from './types';

import { SortFieldCase, StatusAll } from '../../../common/ui/types';
import { CaseStatuses, caseStatuses } from '../../../common/types/domain';
import { SortFieldCase } from '../../../common/ui/types';
import type { CaseStatuses } from '../../../common/types/domain';
import { caseStatuses } from '../../../common/types/domain';
import { OWNER_INFO } from '../../../common/constants';
import { useAvailableCasesOwners } from '../app/use_available_owners';
import { useCasesColumns } from './use_cases_columns';
Expand Down Expand Up @@ -67,7 +63,7 @@ const mapToReadableSolutionName = (solution: string): Solution => {
};

export interface AllCasesListProps {
hiddenStatuses?: CaseStatusWithAllStatus[];
hiddenStatuses?: CaseStatuses[];
isSelectorView?: boolean;
onRowClick?: (theCase?: CaseUI, isCreateCase?: boolean) => void;
}
Expand Down Expand Up @@ -160,22 +156,6 @@ export const AllCasesList = React.memo<AllCasesListProps>(

const onFilterChangedCallback = useCallback(
(newFilterOptions: Partial<FilterOptions>) => {
if (newFilterOptions?.status) {
if (
newFilterOptions.status[0] === CaseStatuses.closed &&
queryParams.sortField === SortFieldCase.createdAt
) {
setQueryParams({ sortField: SortFieldCase.closedAt });
} else if (
[CaseStatuses.open, CaseStatuses['in-progress'], StatusAll].includes(
newFilterOptions.status[0]
) &&
queryParams.sortField === SortFieldCase.closedAt
) {
setQueryParams({ sortField: SortFieldCase.createdAt });
}
}

deselectCases();
setFilterOptions({
...newFilterOptions,
Expand All @@ -199,19 +179,11 @@ export const AllCasesList = React.memo<AllCasesListProps>(
: {}),
});
},
[
queryParams.sortField,
deselectCases,
setFilterOptions,
hasOwner,
availableSolutions,
owner,
setQueryParams,
]
[deselectCases, setFilterOptions, hasOwner, availableSolutions, owner]
);

const { columns } = useCasesColumns({
filterStatus: filterOptions.status ?? StatusAll,
filterStatus: filterOptions.status ?? [],
userProfiles: userProfiles ?? new Map(),
isSelectorView,
connectors,
Expand Down Expand Up @@ -270,22 +242,12 @@ export const AllCasesList = React.memo<AllCasesListProps>(
countInProgressCases={data.countInProgressCases}
onFilterChanged={onFilterChangedCallback}
availableSolutions={hasOwner ? [] : availableSolutionsLabels}
initial={{
search: filterOptions.search,
searchFields: filterOptions.searchFields,
assignees: filterOptions.assignees,
reporters: filterOptions.reporters,
tags: filterOptions.tags,
status: filterOptions.status,
owner: filterOptions.owner,
severity: filterOptions.severity,
category: filterOptions.category,
}}
hiddenStatuses={hiddenStatuses}
onCreateCasePressed={onCreateCasePressed}
isSelectorView={isSelectorView}
isLoading={isLoadingCurrentUserProfile}
currentUserProfile={currentUserProfile}
filterOptions={filterOptions}
/>
<CasesTable
columns={columns}
Expand Down
Loading

0 comments on commit 36d8eba

Please sign in to comment.