Skip to content

Commit

Permalink
[7.x] [SIEM][CASE] Persist callout when dismissed (#68372) (#70110)
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasikas authored Jun 27, 2020
1 parent 30d9772 commit 8d120b5
Show file tree
Hide file tree
Showing 20 changed files with 618 additions and 154 deletions.
3 changes: 2 additions & 1 deletion x-pack/plugins/security_solution/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"test:generate": "ts-node --project scripts/endpoint/cli_tsconfig.json scripts/endpoint/resolver_generator.ts"
},
"devDependencies": {
"@types/lodash": "^4.14.110"
"@types/lodash": "^4.14.110",
"@types/md5": "^2.2.0"
},
"dependencies": {
"lodash": "^4.17.15",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import { i18n } from '@kbn/i18n';
export const NO_WRITE_ALERTS_CALLOUT_TITLE = i18n.translate(
'xpack.securitySolution.detectionEngine.noWriteAlertsCallOutTitle',
{
defaultMessage: 'Alerts index permissions required',
defaultMessage: 'You cannot change alert states',
}
);

export const NO_WRITE_ALERTS_CALLOUT_MSG = i18n.translate(
'xpack.securitySolution.detectionEngine.noWriteAlertsCallOutMsg',
{
defaultMessage:
'You are currently missing the required permissions to update alerts. Please contact your administrator for further assistance.',
'You only have permissions to view alerts. If you need to update alert states (open or close alerts), contact your Kibana administrator.',
}
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { mount } from 'enzyme';

import { CallOut, CallOutProps } from './callout';

describe('Callout', () => {
const defaultProps: CallOutProps = {
id: 'md5-hex',
type: 'primary',
title: 'a tittle',
messages: [
{
id: 'generic-error',
title: 'message-one',
description: <p>{'error'}</p>,
},
],
showCallOut: true,
handleDismissCallout: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
});

it('It renders the callout', () => {
const wrapper = mount(<CallOut {...defaultProps} />);
expect(wrapper.find(`[data-test-subj="case-callout-md5-hex"]`).exists()).toBeTruthy();
expect(wrapper.find(`[data-test-subj="callout-messages-md5-hex"]`).exists()).toBeTruthy();
expect(wrapper.find(`[data-test-subj="callout-dismiss-md5-hex"]`).exists()).toBeTruthy();
});

it('hides the callout', () => {
const wrapper = mount(<CallOut {...defaultProps} showCallOut={false} />);
expect(wrapper.find(`[data-test-subj="case-callout-md5-hex"]`).exists()).toBeFalsy();
});

it('does not shows any messages when the list is empty', () => {
const wrapper = mount(<CallOut {...defaultProps} messages={[]} />);
expect(wrapper.find(`[data-test-subj="callout-messages-md5-hex"]`).exists()).toBeFalsy();
});

it('transform the button color correctly - primary', () => {
const wrapper = mount(<CallOut {...defaultProps} />);
const className =
wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ??
'';
expect(className.includes('euiButton--primary')).toBeTruthy();
});

it('transform the button color correctly - success', () => {
const wrapper = mount(<CallOut {...defaultProps} type={'success'} />);
const className =
wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ??
'';
expect(className.includes('euiButton--secondary')).toBeTruthy();
});

it('transform the button color correctly - warning', () => {
const wrapper = mount(<CallOut {...defaultProps} type={'warning'} />);
const className =
wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ??
'';
expect(className.includes('euiButton--warning')).toBeTruthy();
});

it('transform the button color correctly - danger', () => {
const wrapper = mount(<CallOut {...defaultProps} type={'danger'} />);
const className =
wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ??
'';
expect(className.includes('euiButton--danger')).toBeTruthy();
});

it('dismiss the callout correctly', () => {
const wrapper = mount(<CallOut {...defaultProps} messages={[]} />);
expect(wrapper.find(`[data-test-subj="callout-dismiss-md5-hex"]`).exists()).toBeTruthy();
wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).simulate('click');
wrapper.update();

expect(defaultProps.handleDismissCallout).toHaveBeenCalledWith('md5-hex', 'primary');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiCallOut, EuiButton, EuiDescriptionList } from '@elastic/eui';
import { isEmpty } from 'lodash/fp';
import React, { memo, useCallback } from 'react';

import { ErrorMessage } from './types';
import * as i18n from './translations';

export interface CallOutProps {
id: string;
type: NonNullable<ErrorMessage['errorType']>;
title: string;
messages: ErrorMessage[];
showCallOut: boolean;
handleDismissCallout: (id: string, type: NonNullable<ErrorMessage['errorType']>) => void;
}

const CallOutComponent = ({
id,
type,
title,
messages,
showCallOut,
handleDismissCallout,
}: CallOutProps) => {
const handleCallOut = useCallback(() => handleDismissCallout(id, type), [
handleDismissCallout,
id,
type,
]);

return showCallOut ? (
<EuiCallOut title={title} color={type} iconType="gear" data-test-subj={`case-callout-${id}`}>
{!isEmpty(messages) && (
<EuiDescriptionList data-test-subj={`callout-messages-${id}`} listItems={messages} />
)}
<EuiButton
data-test-subj={`callout-dismiss-${id}`}
color={type === 'success' ? 'secondary' : type}
onClick={handleCallOut}
>
{i18n.DISMISS_CALLOUT}
</EuiButton>
</EuiCallOut>
) : null;
};

export const CallOut = memo(CallOutComponent);
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;
* you may not use this file except in compliance with the Elastic License.
*/

import md5 from 'md5';
import { createCalloutId } from './helpers';

describe('createCalloutId', () => {
it('creates id correctly with one id', () => {
const digest = md5('one');
const id = createCalloutId(['one']);
expect(id).toBe(digest);
});

it('creates id correctly with multiples ids', () => {
const digest = md5('one|two|three');
const id = createCalloutId(['one', 'two', 'three']);
expect(id).toBe(digest);
});

it('creates id correctly with multiples ids and delimiter', () => {
const digest = md5('one,two,three');
const id = createCalloutId(['one', 'two', 'three'], ',');
expect(id).toBe(digest);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import md5 from 'md5';

import * as i18n from './translations';
import { ErrorMessage } from './types';

export const savedObjectReadOnly = {
export const savedObjectReadOnlyErrorMessage: ErrorMessage = {
id: 'read-only-privileges-error',
title: i18n.READ_ONLY_SAVED_OBJECT_TITLE,
description: i18n.READ_ONLY_SAVED_OBJECT_MSG,
description: <>{i18n.READ_ONLY_SAVED_OBJECT_MSG}</>,
errorType: 'warning',
};

export const createCalloutId = (ids: string[], delimiter: string = '|'): string =>
md5(ids.join(delimiter));
Loading

0 comments on commit 8d120b5

Please sign in to comment.