Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cases] Refactor Cases List Filters #169371

Merged
merged 36 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
dd7e233
first commit
jcger Oct 19, 2023
5967129
delete unused imports
jcger Oct 19, 2023
e664592
add new status filter component
jcger Oct 19, 2023
9ad3273
Merge branch 'cases/167651' of github.com:elastic/kibana into issue-1…
jcger Oct 30, 2023
9f9c1ff
ui and severity filter
jcger Oct 30, 2023
63496c4
remove All status and severity option
jcger Oct 30, 2023
1b2a072
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Oct 30, 2023
61bbb7f
remove all
jcger Oct 30, 2023
afa37a4
Merge branch 'issue-167651-filters-refactor' of github.com:jcger/kiba…
jcger Oct 30, 2023
5f01dc1
sanitize
jcger Oct 30, 2023
eb573c1
various fixes
jcger Oct 30, 2023
24e9b30
testing
jcger Nov 2, 2023
c017738
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Nov 2, 2023
1124e35
tests and linter
jcger Nov 3, 2023
ed782e8
Merge branch 'issue-167651-filters-refactor' of github.com:jcger/kiba…
jcger Nov 3, 2023
248cb73
Merge branch 'cases/167651' into issue-167651-filters-refactor
jcger Nov 3, 2023
6725763
forgotten code
jcger Nov 3, 2023
4c04c19
test and linting
jcger Nov 3, 2023
c81f2a6
removed sorting depending on status
jcger Nov 3, 2023
30ca22d
fix ci
jcger Nov 6, 2023
86054db
copy list view updates in solutions
jcger Nov 6, 2023
5427b93
review and remove invalid layout when limit reached
jcger Nov 6, 2023
4b974fb
fix translation key
jcger Nov 6, 2023
819c8d8
Merge branch 'cases/167651' into issue-167651-filters-refactor
jcger Nov 6, 2023
d045f2d
review + ci
jcger Nov 6, 2023
42c3345
Merge branch 'issue-167651-filters-refactor' of github.com:jcger/kiba…
jcger Nov 6, 2023
e585063
review
jcger Nov 6, 2023
b3018be
review + ci
jcger Nov 6, 2023
42b22cd
ci fixes
jcger Nov 7, 2023
3d26280
Merge branch 'cases/167651' into issue-167651-filters-refactor
jcger Nov 7, 2023
1bb0a46
fix ci
jcger Nov 7, 2023
7d644b1
remove "all" translations
jcger Nov 7, 2023
d8c539f
review
jcger Nov 7, 2023
93b5484
Update x-pack/plugins/cases/public/components/all_cases/multi_select_…
jcger Nov 7, 2023
e2f7f11
fix test
jcger Nov 8, 2023
379d7d2
Merge branch 'issue-167651-filters-refactor' of github.com:jcger/kiba…
jcger Nov 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -512,9 +515,9 @@ describe('AllCasesListGeneric', () => {

it('should filter by status: closed', async () => {
appMockRenderer.render(<AllCasesList isSelectorView={false} />);
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-closed'));
await waitFor(() => {
expect(useGetCasesMock).toHaveBeenLastCalledWith(
expect.objectContaining({
Expand All @@ -526,9 +529,9 @@ describe('AllCasesListGeneric', () => {

it('should filter by status: in-progress', async () => {
appMockRenderer.render(<AllCasesList isSelectorView={false} />);
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-in-progress'));
userEvent.click(screen.getByTestId('options-filter-popover-item-in-progress'));
await waitFor(() => {
expect(useGetCasesMock).toHaveBeenLastCalledWith(
expect.objectContaining({
Expand All @@ -540,9 +543,9 @@ describe('AllCasesListGeneric', () => {

it('should filter by status: open', async () => {
appMockRenderer.render(<AllCasesList isSelectorView={false} />);
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-in-progress'));
userEvent.click(screen.getByTestId('options-filter-popover-item-open'));
await waitFor(() => {
expect(useGetCasesMock).toHaveBeenLastCalledWith(
expect.objectContaining({
Expand All @@ -555,20 +558,23 @@ describe('AllCasesListGeneric', () => {
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 () => {
// FIXME: This was checking for the button label, this does not exists anymore. Maybe we can get rid of this test
it.skip('renders the first available status when hiddenStatus is given', async () => {
appMockRenderer.render(
<AllCasesList hiddenStatuses={[StatusAll, CaseStatuses.open]} isSelectorView={true} />
<AllCasesList hiddenStatuses={[CaseStatuses.open]} isSelectorView={true} />
);

await waitFor(() =>
Expand Down Expand Up @@ -632,9 +638,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 +698,9 @@ describe('AllCasesListGeneric', () => {
filterOptions: {
search: '',
searchFields: ['title', 'description'],
severity: ['all'],
severity: [],
reporters: [],
status: ['all'],
status: [],
tags: [],
assignees: [],
owner: ['securitySolution', 'observability'],
Expand All @@ -719,9 +725,9 @@ describe('AllCasesListGeneric', () => {
filterOptions: {
search: '',
searchFields: ['title', 'description'],
severity: ['all'],
severity: [],
reporters: [],
status: ['all'],
status: [],
tags: [],
assignees: [],
owner: ['securitySolution'],
Expand All @@ -742,9 +748,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 +781,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,16 +11,11 @@ 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 { SortFieldCase } from '../../../common/ui/types';
import { CaseStatuses, caseStatuses } from '../../../common/types/domain';
import { OWNER_INFO } from '../../../common/constants';
import { useAvailableCasesOwners } from '../app/use_available_owners';
Expand Down Expand Up @@ -67,7 +62,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,16 +155,15 @@ export const AllCasesList = React.memo<AllCasesListProps>(

const onFilterChangedCallback = useCallback(
(newFilterOptions: Partial<FilterOptions>) => {
// FIXME: how should this work with multiple options?
jcger marked this conversation as resolved.
Show resolved Hide resolved
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]
) &&
[CaseStatuses.open, CaseStatuses['in-progress']].includes(newFilterOptions.status[0]) &&
queryParams.sortField === SortFieldCase.closedAt
) {
setQueryParams({ sortField: SortFieldCase.createdAt });
Expand Down Expand Up @@ -211,7 +205,7 @@ export const AllCasesList = React.memo<AllCasesListProps>(
);

const { columns } = useCasesColumns({
filterStatus: filterOptions.status ?? StatusAll,
filterStatus: filterOptions.status ?? [],
userProfiles: userProfiles ?? new Map(),
isSelectorView,
connectors,
Expand Down Expand Up @@ -270,22 +264,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