Skip to content

Commit

Permalink
[Cases] Add severity field to the Case view page. (#131521)
Browse files Browse the repository at this point in the history
  • Loading branch information
academo authored May 6, 2022
1 parent 2fd0e55 commit 741d7e9
Show file tree
Hide file tree
Showing 12 changed files with 342 additions and 5 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/cases/common/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export interface FieldMappings {

export type UpdateKey = keyof Pick<
CasePatchRequest,
'connector' | 'description' | 'status' | 'tags' | 'title' | 'settings'
'connector' | 'description' | 'status' | 'tags' | 'title' | 'settings' | 'severity'
>;

export interface UpdateByKey {
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/cases/public/common/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ export const MARKED_CASE_AS = i18n.translate('xpack.cases.caseView.markedCaseAs'
defaultMessage: 'marked case as',
});

export const SET_SEVERITY_TO = i18n.translate('xpack.cases.caseView.setSeverityTo', {
defaultMessage: 'set severity to',
});

export const OPEN_CASES = i18n.translate('xpack.cases.caseTable.openCases', {
defaultMessage: 'Open cases',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { EuiFlexGroup, EuiFlexItem, EuiLoadingContent } from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';
import { CaseSeverity } from '../../../../common/api';
import { useConnectors } from '../../../containers/configure/use_connectors';
import { useCaseViewNavigation } from '../../../common/navigation';
import { UpdateKey, UseFetchAlertData } from '../../../../common/ui/types';
Expand All @@ -23,6 +24,7 @@ import * as i18n from '../translations';
import { getNoneConnector, normalizeActionConnector } from '../../configure_cases/utils';
import { getConnectorById } from '../../utils';
import { UseGetCaseUserActions } from '../../../containers/use_get_case_user_actions';
import { SeveritySidebarSelector } from '../../severity/sidebar_selector';

export const CaseViewActivity = ({
initLoadingData,
Expand Down Expand Up @@ -108,6 +110,12 @@ export const CaseViewActivity = ({
(newTags) => onUpdateField({ key: 'tags', value: newTags }),
[onUpdateField]
);

const onUpdateSeverity = useCallback(
(newSeverity: CaseSeverity) => onUpdateField({ key: 'severity', value: newSeverity }),
[onUpdateField]
);

const { loading: isLoadingConnectors, connectors } = useConnectors();

const [connectorName, isValidConnector] = useMemo(() => {
Expand Down Expand Up @@ -180,6 +188,12 @@ export const CaseViewActivity = ({
)}
</EuiFlexItem>
<EuiFlexItem grow={2}>
<SeveritySidebarSelector
isDisabled={!userCanCrud}
isLoading={isLoading}
selectedSeverity={caseData.severity}
onSeverityChange={onUpdateSeverity}
/>
<UserList
data-test-subj="case-view-user-list-reporter"
email={emailContent}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ export const useOnUpdateField = ({
callUpdate('settings', settingsUpdate);
}
break;
case 'severity':
const severityUpdate = getTypedPayload<CaseAttributes['severity']>(value);
if (caseData.severity !== value) {
callUpdate('severity', severityUpdate);
}
default:
return null;
}
Expand Down
29 changes: 29 additions & 0 deletions x-pack/plugins/cases/public/components/severity/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 { euiLightVars } from '@kbn/ui-theme';
import { CaseSeverity } from '../../../common/api';
import { CRITICAL, HIGH, LOW, MEDIUM } from './translations';

export const severities = {
[CaseSeverity.LOW]: {
color: euiLightVars.euiColorVis0,
label: LOW,
},
[CaseSeverity.MEDIUM]: {
color: euiLightVars.euiColorVis5,
label: MEDIUM,
},
[CaseSeverity.HIGH]: {
color: euiLightVars.euiColorVis7,
label: HIGH,
},
[CaseSeverity.CRITICAL]: {
color: euiLightVars.euiColorVis9,
label: CRITICAL,
},
};
60 changes: 60 additions & 0 deletions x-pack/plugins/cases/public/components/severity/selector.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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 { render } from '@testing-library/react';
import React from 'react';
import { SeveritySelector } from './selector';
import userEvent from '@testing-library/user-event';

describe('Severity field selector', () => {
const onSeverityChange = jest.fn();
it('renders a list of severity fields', () => {
const result = render(
<SeveritySelector
selectedSeverity={CaseSeverity.MEDIUM}
onSeverityChange={onSeverityChange}
isLoading={false}
isDisabled={false}
/>
);

expect(result.getByTestId('case-severity-selection')).toBeTruthy();
expect(result.getAllByTestId('case-severity-selection-medium').length).toBeTruthy();
});

it('renders a list of severity options when clicked', () => {
const result = render(
<SeveritySelector
selectedSeverity={CaseSeverity.MEDIUM}
onSeverityChange={onSeverityChange}
isLoading={false}
isDisabled={false}
/>
);
userEvent.click(result.getByTestId('case-severity-selection'));
expect(result.getByTestId('case-severity-selection-low')).toBeTruthy();
expect(result.getAllByTestId('case-severity-selection-medium').length).toBeTruthy();
expect(result.getByTestId('case-severity-selection-high')).toBeTruthy();
expect(result.getByTestId('case-severity-selection-critical')).toBeTruthy();
});

it('calls onSeverityChange with the newly selected severity when clicked', () => {
const result = render(
<SeveritySelector
selectedSeverity={CaseSeverity.MEDIUM}
onSeverityChange={onSeverityChange}
isLoading={false}
isDisabled={false}
/>
);
userEvent.click(result.getByTestId('case-severity-selection'));
expect(result.getByTestId('case-severity-selection-low')).toBeTruthy();
userEvent.click(result.getByTestId('case-severity-selection-low'));
expect(onSeverityChange).toHaveBeenLastCalledWith('low');
});
});
64 changes: 64 additions & 0 deletions x-pack/plugins/cases/public/components/severity/selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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,
} from '@elastic/eui';
import React from 'react';
import { CaseSeverity } from '../../../common/api';
import { severities } from './config';

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

export const SeveritySelector: React.FC<Props> = ({
selectedSeverity,
onSeverityChange,
isLoading,
isDisabled,
}) => {
const caseSeverities = Object.keys(severities) as CaseSeverity[];
const options: Array<EuiSuperSelectOption<CaseSeverity>> = caseSeverities.map((severity) => {
const severityData = severities[severity];
return {
value: severity,
inputDisplay: (
<EuiFlexGroup
gutterSize="xs"
alignItems={'center'}
responsive={false}
data-test-subj={`case-severity-selection-${severity}`}
>
<EuiFlexItem grow={false}>
<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-selection"
/>
);
};
SeveritySelector.displayName = 'SeveritySelector';
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 { EuiFlexItem, EuiHorizontalRule, EuiSpacer, EuiText } from '@elastic/eui';
import React from 'react';
import { CaseSeverity } from '../../../common/api';
import { SeveritySelector } from './selector';
import { SEVERITY_TITLE } from './translations';

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

export const SeveritySidebarSelector: React.FC<Props> = ({
selectedSeverity,
onSeverityChange,
isLoading,
isDisabled,
}) => {
return (
<EuiFlexItem grow={false}>
<EuiText>
<h4>{SEVERITY_TITLE}</h4>
</EuiText>
<EuiHorizontalRule margin="xs" />
<SeveritySelector
isLoading={isLoading}
selectedSeverity={selectedSeverity}
onSeverityChange={onSeverityChange}
isDisabled={isDisabled}
/>
<EuiSpacer size="m" />
</EuiFlexItem>
);
};
SeveritySidebarSelector.displayName = 'SeveritySidebarSelector';
28 changes: 28 additions & 0 deletions x-pack/plugins/cases/public/components/severity/translations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 { i18n } from '@kbn/i18n';

export const LOW = i18n.translate('xpack.cases.severity.low', {
defaultMessage: 'Low',
});

export const MEDIUM = i18n.translate('xpack.cases.severity.medium', {
defaultMessage: 'Medium',
});

export const HIGH = i18n.translate('xpack.cases.severity.high', {
defaultMessage: 'High',
});

export const CRITICAL = i18n.translate('xpack.cases.severity.critical', {
defaultMessage: 'Critical',
});

export const SEVERITY_TITLE = i18n.translate('xpack.cases.severity.title', {
defaultMessage: 'Severity',
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { createConnectorUserActionBuilder } from './connector';
import { createDescriptionUserActionBuilder } from './description';
import { createPushedUserActionBuilder } from './pushed';
import { createSettingsUserActionBuilder } from './settings';
import { createSeverityUserActionBuilder } from './severity';
import { createStatusUserActionBuilder } from './status';
import { createTagsUserActionBuilder } from './tags';
import { createTitleUserActionBuilder } from './title';
Expand All @@ -20,10 +21,7 @@ export const builderMap: UserActionBuilderMap = {
tags: createTagsUserActionBuilder,
title: createTitleUserActionBuilder,
status: createStatusUserActionBuilder,
// TODO: Build severity user action
severity: () => ({
build: () => [],
}),
severity: createSeverityUserActionBuilder,
pushed: createPushedUserActionBuilder,
comment: createCommentUserActionBuilder,
description: createDescriptionUserActionBuilder,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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 { EuiCommentList } from '@elastic/eui';
import { Actions, CaseSeverity } from '../../../common/api';
import React from 'react';
import { AppMockRenderer, createAppMockRenderer } from '../../common/mock';
import { getUserAction } from '../../containers/mock';
import { getMockBuilderArgs } from './mock';
import { createSeverityUserActionBuilder } from './severity';

jest.mock('../../common/lib/kibana');
jest.mock('../../common/navigation/hooks');

const builderArgs = getMockBuilderArgs();
describe('createSeverityUserActionBuilder', () => {
let appMockRenderer: AppMockRenderer;
beforeEach(() => {
appMockRenderer = createAppMockRenderer();
});
it('renders correctly', () => {
const userAction = getUserAction('severity', Actions.update, {
payload: { severity: CaseSeverity.LOW },
});
const builder = createSeverityUserActionBuilder({
...builderArgs,
userAction,
});
const createdUserAction = builder.build();

const result = appMockRenderer.render(<EuiCommentList comments={createdUserAction} />);
expect(result.getByTestId('severity-update-user-action-severity-title')).toBeTruthy();
expect(result.getByTestId('severity-update-user-action-severity-title-low')).toBeTruthy();
});
});
Loading

0 comments on commit 741d7e9

Please sign in to comment.