Skip to content

Commit

Permalink
[Cases] severity field in the cases list and allow to filter by it (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
academo committed May 9, 2022
1 parent 8166d62 commit 190ed55
Show file tree
Hide file tree
Showing 25 changed files with 423 additions and 39 deletions.
4 changes: 4 additions & 0 deletions x-pack/plugins/cases/common/api/cases/case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ export const CasesFindRequestRt = rt.partial({
* The status of the case (open, closed, in-progress)
*/
status: CaseStatusRt,
/**
* The severity of the case
*/
severity: CaseSeverityRt,
/**
* The reporters to filter by
*/
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/cases/common/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
CasesFindResponse,
CasesStatusResponse,
CasesMetricsResponse,
CaseSeverity,
} from '../api';
import { SnakeToCamelCase } from '../types';

Expand All @@ -45,6 +46,9 @@ export type StatusAllType = typeof StatusAll;

export type CaseStatusWithAllStatus = CaseStatuses | StatusAllType;

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

/**
* The type for the `refreshRef` prop (a `React.Ref`) defined by the `CaseViewComponentProps`.
*
Expand Down Expand Up @@ -84,6 +88,7 @@ export interface QueryParams {

export interface FilterOptions {
search: string;
severity: CaseSeverityWithAll;
status: CaseStatusWithAllStatus;
tags: string[];
reporters: User[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ describe('AllCasesListGeneric', () => {
.childAt(0)
.prop('value')
).toBe(useGetCasesMockState.data.cases[0].createdAt);

expect(
wrapper.find(`[data-test-subj="case-table-column-severity"]`).first().text().toLowerCase()
).toBe(useGetCasesMockState.data.cases[0].severity);

expect(wrapper.find(`[data-test-subj="case-table-case-count"]`).first().text()).toEqual(
'Showing 10 cases'
);
Expand All @@ -223,6 +228,7 @@ describe('AllCasesListGeneric', () => {
createdAt: null,
createdBy: null,
status: null,
severity: null,
tags: null,
title: null,
totalComment: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ export const AllCasesList = React.memo<AllCasesListProps>(
tags: filterOptions.tags,
status: filterOptions.status,
owner: filterOptions.owner,
severity: filterOptions.severity,
}}
setFilterRefetch={setFilterRefetch}
hiddenStatuses={hiddenStatuses}
Expand Down
67 changes: 42 additions & 25 deletions x-pack/plugins/cases/public/components/all_cases/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiHealth,
} from '@elastic/eui';
import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services';
import styled from 'styled-components';

import { Case, DeleteCase } from '../../../common/ui/types';
import { CaseStatuses, ActionConnector } from '../../../common/api';
import { CaseStatuses, ActionConnector, CaseSeverity } from '../../../common/api';
import { OWNER_INFO } from '../../../common/constants';
import { getEmptyTagValue } from '../empty_value';
import { FormattedRelativePreferenceDate } from '../formatted_date';
Expand All @@ -40,6 +41,7 @@ import { TruncatedText } from '../truncated_text';
import { getConnectorIcon } from '../utils';
import type { CasesOwners } from '../../client/helpers/can_use_cases';
import { useCasesFeatures } from '../cases_context/use_cases_features';
import { severities } from '../severity/config';

export type CasesColumns =
| EuiTableActionsColumnType<Case>
Expand Down Expand Up @@ -300,30 +302,6 @@ export const useCasesColumns = ({
return getEmptyTagValue();
},
},
...(isSelectorView
? [
{
align: RIGHT_ALIGNMENT,
render: (theCase: Case) => {
if (theCase.id != null) {
return (
<EuiButton
data-test-subj={`cases-table-row-select-${theCase.id}`}
onClick={() => {
assignCaseAction(theCase);
}}
size="s"
fill={true}
>
{i18n.SELECT}
</EuiButton>
);
}
return getEmptyTagValue();
},
},
]
: []),
...(!isSelectorView
? [
{
Expand Down Expand Up @@ -351,6 +329,45 @@ export const useCasesColumns = ({
},
]
: []),
{
name: i18n.SEVERITY,
render: (theCase: Case) => {
if (theCase.severity != null) {
const severityData = severities[theCase.severity ?? CaseSeverity.LOW];
return (
<EuiHealth data-test-subj="case-table-column-severity" color={severityData.color}>
{severityData.label}
</EuiHealth>
);
}
return getEmptyTagValue();
},
},

...(isSelectorView
? [
{
align: RIGHT_ALIGNMENT,
render: (theCase: Case) => {
if (theCase.id != null) {
return (
<EuiButton
data-test-subj={`cases-table-row-select-${theCase.id}`}
onClick={() => {
assignCaseAction(theCase);
}}
size="s"
fill={true}
>
{i18n.SELECT}
</EuiButton>
);
}
return getEmptyTagValue();
},
},
]
: []),
...(userCanCrud && !isSelectorView
? [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { CaseSeverity } from '../../../common/api';
import React from 'react';
import { AppMockRenderer, createAppMockRenderer } from '../../common/mock';
import userEvent from '@testing-library/user-event';
import { waitFor } from '@testing-library/dom';
import { SeverityFilter } from './severity_filter';

describe('Severity form field', () => {
const onSeverityChange = jest.fn();
let appMockRender: AppMockRenderer;
const props = {
isLoading: false,
selectedSeverity: CaseSeverity.LOW,
isDisabled: false,
onSeverityChange,
};
beforeEach(() => {
appMockRender = createAppMockRenderer();
});
it('renders', () => {
const result = appMockRender.render(<SeverityFilter {...props} />);
expect(result.getByTestId('case-severity-filter')).not.toHaveAttribute('disabled');
});

// default to LOW in this test configuration
it('defaults to the correct value', () => {
const result = appMockRender.render(<SeverityFilter {...props} />);
// two items. one for the popover one for the selected field
expect(result.getAllByTestId('case-severity-filter-low').length).toBe(2);
});

it('selects the correct value when changed', async () => {
const result = appMockRender.render(<SeverityFilter {...props} />);
userEvent.click(result.getByTestId('case-severity-filter'));
userEvent.click(result.getByTestId('case-severity-filter-high'));
await waitFor(() => {
expect(onSeverityChange).toHaveBeenCalledWith('high');
});
});

it('selects the correct value when changed (all)', async () => {
const result = appMockRender.render(<SeverityFilter {...props} />);
userEvent.click(result.getByTestId('case-severity-filter'));
userEvent.click(result.getByTestId('case-severity-filter-all'));
await waitFor(() => {
expect(onSeverityChange).toHaveBeenCalledWith('all');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import {
EuiFlexGroup,
EuiFlexItem,
EuiHealth,
EuiSuperSelect,
EuiSuperSelectOption,
EuiText,
} from '@elastic/eui';
import React from 'react';
import { CaseSeverityWithAll, SeverityAll } from '../../containers/types';
import { severitiesWithAll } from '../severity/config';

interface Props {
selectedSeverity: CaseSeverityWithAll;
onSeverityChange: (status: CaseSeverityWithAll) => void;
isLoading: boolean;
isDisabled: boolean;
}

export const SeverityFilter: React.FC<Props> = ({
selectedSeverity,
onSeverityChange,
isLoading,
isDisabled,
}) => {
const caseSeverities = Object.keys(severitiesWithAll) as CaseSeverityWithAll[];
const options: Array<EuiSuperSelectOption<CaseSeverityWithAll>> = caseSeverities.map(
(severity) => {
const severityData = severitiesWithAll[severity];
return {
value: severity,
inputDisplay: (
<EuiFlexGroup
gutterSize="xs"
alignItems={'center'}
responsive={false}
data-test-subj={`case-severity-filter-${severity}`}
>
<EuiFlexItem grow={false}>
{severity === SeverityAll ? (
<EuiText size="s">{severityData.label}</EuiText>
) : (
<EuiHealth color={severityData.color}>{severityData.label}</EuiHealth>
)}
</EuiFlexItem>
</EuiFlexGroup>
),
};
}
);

return (
<EuiSuperSelect
disabled={isDisabled}
fullWidth={true}
isLoading={isLoading}
options={options}
valueOfSelected={selectedSeverity}
onChange={onSeverityChange}
data-test-subj="case-severity-filter"
/>
);
};
SeverityFilter.displayName = 'SeverityFilter';
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import { mount } from 'enzyme';

import { CaseStatuses } from '../../../common/api';
import { OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { TestProviders } from '../../common/mock';
import { AppMockRenderer, createAppMockRenderer, TestProviders } from '../../common/mock';
import { useGetTags } from '../../containers/use_get_tags';
import { useGetReporters } from '../../containers/use_get_reporters';
import { DEFAULT_FILTER_OPTIONS } from '../../containers/use_get_cases';
import { CasesTableFilters } from './table_filters';
import userEvent from '@testing-library/user-event';

jest.mock('../../containers/use_get_reporters');
jest.mock('../../containers/use_get_tags');
Expand All @@ -35,7 +36,9 @@ const props = {
};

describe('CasesTableFilters ', () => {
let appMockRender: AppMockRenderer;
beforeEach(() => {
appMockRender = createAppMockRenderer();
jest.clearAllMocks();
(useGetTags as jest.Mock).mockReturnValue({ tags: ['coke', 'pepsi'], fetchTags });
(useGetReporters as jest.Mock).mockReturnValue({
Expand All @@ -57,6 +60,19 @@ describe('CasesTableFilters ', () => {
expect(wrapper.find(`[data-test-subj="case-status-filter"]`).first().exists()).toBeTruthy();
});

it('should render the case severity filter dropdown', () => {
const result = appMockRender.render(<CasesTableFilters {...props} />);
expect(result.getByTestId('case-severity-filter')).toBeTruthy();
});

it('should call onFilterChange when the severity filter changes', () => {
const result = appMockRender.render(<CasesTableFilters {...props} />);
userEvent.click(result.getByTestId('case-severity-filter'));
userEvent.click(result.getByTestId('case-severity-filter-high'));

expect(onFilterChanged).toBeCalledWith({ severity: 'high' });
});

it('should call onFilterChange when selected tags change', () => {
const wrapper = mount(
<TestProviders>
Expand Down
Loading

0 comments on commit 190ed55

Please sign in to comment.