From 318147a4acf4df817a8b99e727eb26bf8a2e092a Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Wed, 10 Jun 2020 08:08:57 -0400 Subject: [PATCH] [SIEM][Detections Engine] - Exceptions viewer cleanup (#68651) ### Summary This PR is a follow up to #68027 where some feedback didn't make it in. It cleans up the and_or_badge component, updates some css, and cleans up stories. --- .../__examples__/index.stories.tsx | 37 ++++---- .../components/and_or_badge/index.test.tsx | 18 +++- .../common/components/and_or_badge/index.tsx | 93 +------------------ .../and_or_badge/rounded_badge.test.tsx | 34 +++++++ .../components/and_or_badge/rounded_badge.tsx | 42 +++++++++ .../rounded_badge_antenna.test.tsx | 46 +++++++++ .../and_or_badge/rounded_badge_antenna.tsx | 62 +++++++++++++ .../__examples__/exception_item.stories.tsx | 89 +++++++++--------- .../components/exceptions/helpers.test.tsx | 8 +- .../common/components/exceptions/helpers.tsx | 4 +- .../exception_item/exception_details.tsx | 51 +++++----- .../exception_item/exception_entries.test.tsx | 34 +++---- .../exception_item/exception_entries.tsx | 28 ++++-- .../viewer/exception_item/index.test.tsx | 36 +++---- .../viewer/exception_item/index.tsx | 32 +++---- .../components/exceptions/viewer/index.tsx | 8 +- .../security_solution/public/graphql/types.ts | 2 + .../security_solution/scripts/storybook.js | 2 +- 18 files changed, 371 insertions(+), 255 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge_antenna.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge_antenna.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/and_or_badge/__examples__/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/and_or_badge/__examples__/index.stories.tsx index f939cf81d1bd3d..7465d3ca1e63ac 100644 --- a/x-pack/plugins/security_solution/public/common/components/and_or_badge/__examples__/index.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/and_or_badge/__examples__/index.stories.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { storiesOf } from '@storybook/react'; -import React from 'react'; +import React, { ReactNode } from 'react'; import { ThemeProvider } from 'styled-components'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; @@ -14,26 +14,21 @@ import { AndOrBadge } from '..'; const sampleText = 'Doggo ipsum i am bekom fat snoot wow such tempt waggy wags floofs, ruff heckin good boys and girls mlem. Ruff heckin good boys and girls mlem stop it fren borkf borking doggo very hand that feed shibe, you are doing me the shock big ol heck smol borking doggo with a long snoot for pats heckin good boys. You are doing me the shock smol borking doggo with a long snoot for pats wow very biscit, length boy. Doggo ipsum i am bekom fat snoot wow such tempt waggy wags floofs, ruff heckin good boys and girls mlem. Ruff heckin good boys and girls mlem stop it fren borkf borking doggo very hand that feed shibe, you are doing me the shock big ol heck smol borking doggo with a long snoot for pats heckin good boys.'; +const withTheme = (storyFn: () => ReactNode) => ( + ({ eui: euiLightVars, darkMode: true })}>{storyFn()} +); + storiesOf('components/AndOrBadge', module) - .add('and', () => ( - ({ eui: euiLightVars, darkMode: true })}> - - - )) - .add('or', () => ( - ({ eui: euiLightVars, darkMode: true })}> - - - )) + .addDecorator(withTheme) + .add('and', () => ) + .add('or', () => ) .add('antennas', () => ( - ({ eui: euiLightVars, darkMode: true })}> - - - - - -

{sampleText}

-
-
-
+ + + + + +

{sampleText}

+
+
)); diff --git a/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.test.tsx index ed918a59a514a0..f2c7d6884bae30 100644 --- a/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.test.tsx @@ -20,8 +20,20 @@ describe('AndOrBadge', () => { ); expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('AND'); - expect(wrapper.find('EuiFlexItem[data-test-subj="andOrBadgeBarTop"]')).toHaveLength(1); - expect(wrapper.find('EuiFlexItem[data-test-subj="andOrBadgeBarBottom"]')).toHaveLength(1); + expect(wrapper.find('[data-test-subj="andOrBadgeBarTop"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="andOrBadgeBarBottom"]').exists()).toBeTruthy(); + }); + + test('it does not render top and bottom antenna bars when "includeAntennas" is false', () => { + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('OR'); + expect(wrapper.find('[data-test-subj="andOrBadgeBarTop"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="andOrBadgeBarBottom"]').exists()).toBeFalsy(); }); test('it renders "and" when "type" is "and"', () => { @@ -32,7 +44,6 @@ describe('AndOrBadge', () => { ); expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('AND'); - expect(wrapper.find('EuiFlexItem[data-test-subj="and-or-badge-bar"]')).toHaveLength(0); }); test('it renders "or" when "type" is "or"', () => { @@ -43,6 +54,5 @@ describe('AndOrBadge', () => { ); expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('OR'); - expect(wrapper.find('EuiFlexItem[data-test-subj="and-or-badge-bar"]')).toHaveLength(0); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.tsx b/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.tsx index ba3f880d9757eb..e427e57a2c6163 100644 --- a/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/and_or_badge/index.tsx @@ -3,70 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { EuiFlexGroup, EuiBadge, EuiFlexItem } from '@elastic/eui'; import React from 'react'; -import styled, { css } from 'styled-components'; - -import * as i18n from './translations'; - -const AndOrBadgeAntenna = styled(EuiFlexItem)` - ${({ theme }) => css` - background: ${theme.eui.euiColorLightShade}; - position: relative; - width: 2px; - &:after { - background: ${theme.eui.euiColorLightShade}; - content: ''; - height: 8px; - right: -4px; - position: absolute; - width: 9px; - clip-path: circle(); - } - &.topAndOrBadgeAntenna { - &:after { - top: -1px; - } - } - &.bottomAndOrBadgeAntenna { - &:after { - bottom: -1px; - } - } - &.euiFlexItem { - margin: 0 12px 0 0; - } - `} -`; - -const EuiFlexItemWrapper = styled(EuiFlexItem)` - &.euiFlexItem { - margin: 0 12px 0 0; - } -`; -const RoundedBadge = (styled(EuiBadge)` - align-items: center; - border-radius: 100%; - display: inline-flex; - font-size: 9px; - height: 34px; - justify-content: center; - margin: 0 5px 0 5px; - padding: 7px 6px 4px 6px; - user-select: none; - width: 34px; - .euiBadge__content { - position: relative; - top: -1px; - } - .euiBadge__text { - text-overflow: clip; - } -` as unknown) as typeof EuiBadge; - -RoundedBadge.displayName = 'RoundedBadge'; +import { RoundedBadge } from './rounded_badge'; +import { RoundedBadgeAntenna } from './rounded_badge_antenna'; export type AndOr = 'and' | 'or'; @@ -74,34 +14,7 @@ export type AndOr = 'and' | 'or'; // Ref: https://github.com/elastic/eui/issues/1655 export const AndOrBadge = React.memo<{ type: AndOr; includeAntennas?: boolean }>( ({ type, includeAntennas = false }) => { - const getBadge = () => ( - - {type === 'and' ? i18n.AND : i18n.OR} - - ); - - const getBadgeWithAntennas = () => ( - - - {getBadge()} - - - ); - - return includeAntennas ? getBadgeWithAntennas() : getBadge(); + return includeAntennas ? : ; } ); diff --git a/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge.test.tsx b/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge.test.tsx new file mode 100644 index 00000000000000..14d9627d48ad70 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge.test.tsx @@ -0,0 +1,34 @@ +/* + * 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 { ThemeProvider } from 'styled-components'; +import { mount } from 'enzyme'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; + +import { RoundedBadge } from './rounded_badge'; + +describe('RoundedBadge', () => { + test('it renders "and" when "type" is "and"', () => { + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('AND'); + }); + + test('it renders "or" when "type" is "or"', () => { + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('OR'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge.tsx b/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge.tsx new file mode 100644 index 00000000000000..1a03e8c73f2522 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge.tsx @@ -0,0 +1,42 @@ +/* + * 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 { EuiBadge } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; + +import * as i18n from './translations'; +import { AndOr } from '.'; + +const RoundBadge = (styled(EuiBadge)` + align-items: center; + border-radius: 100%; + display: inline-flex; + font-size: 9px; + height: 34px; + justify-content: center; + margin: 0 5px 0 5px; + padding: 7px 6px 4px 6px; + user-select: none; + width: 34px; + .euiBadge__content { + position: relative; + top: -1px; + } + .euiBadge__text { + text-overflow: clip; + } +` as unknown) as typeof EuiBadge; + +RoundBadge.displayName = 'RoundBadge'; + +export const RoundedBadge: React.FC<{ type: AndOr }> = ({ type }) => ( + + {type === 'and' ? i18n.AND : i18n.OR} + +); + +RoundedBadge.displayName = 'RoundedBadge'; diff --git a/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge_antenna.test.tsx b/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge_antenna.test.tsx new file mode 100644 index 00000000000000..e6362f8798fa61 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge_antenna.test.tsx @@ -0,0 +1,46 @@ +/* + * 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 { ThemeProvider } from 'styled-components'; +import { mount } from 'enzyme'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; + +import { RoundedBadgeAntenna } from './rounded_badge_antenna'; + +describe('RoundedBadgeAntenna', () => { + test('it renders top and bottom antenna bars', () => { + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('AND'); + expect(wrapper.find('[data-test-subj="andOrBadgeBarTop"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="andOrBadgeBarBottom"]').exists()).toBeTruthy(); + }); + + test('it renders "and" when "type" is "and"', () => { + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('AND'); + }); + + test('it renders "or" when "type" is "or"', () => { + const wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + + expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('OR'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge_antenna.tsx b/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge_antenna.tsx new file mode 100644 index 00000000000000..1076d8b41b9556 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge_antenna.tsx @@ -0,0 +1,62 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import styled, { css } from 'styled-components'; + +import { RoundedBadge } from './rounded_badge'; +import { AndOr } from '.'; + +const antennaStyles = css` + background: ${({ theme }) => theme.eui.euiColorLightShade}; + position: relative; + width: 2px; + margin: 0 12px 0 0; + &:after { + background: ${({ theme }) => theme.eui.euiColorLightShade}; + content: ''; + height: 8px; + right: -4px; + position: absolute; + width: 10px; + clip-path: circle(); + } +`; + +const TopAntenna = styled(EuiFlexItem)` + ${antennaStyles} + &:after { + top: 0; + } +`; +const BottomAntenna = styled(EuiFlexItem)` + ${antennaStyles} + &:after { + bottom: 0; + } +`; + +const EuiFlexItemWrapper = styled(EuiFlexItem)` + margin: 0 12px 0 0; +`; + +export const RoundedBadgeAntenna: React.FC<{ type: AndOr }> = ({ type }) => ( + + + + + + + +); + +RoundedBadgeAntenna.displayName = 'RoundedBadgeAntenna'; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/__examples__/exception_item.stories.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/__examples__/exception_item.stories.tsx index 8942832798a5e5..5f2b0b93e9df02 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/__examples__/exception_item.stories.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/__examples__/exception_item.stories.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { storiesOf } from '@storybook/react'; -import React from 'react'; +import React, { ReactNode } from 'react'; import { ThemeProvider } from 'styled-components'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; @@ -12,8 +12,13 @@ import { ExceptionItem } from '../viewer/exception_item'; import { Operator } from '../types'; import { getExceptionItemMock } from '../mocks'; +const withTheme = (storyFn: () => ReactNode) => ( + ({ eui: euiLightVars, darkMode: false })}>{storyFn()} +); + storiesOf('ExceptionItem', module) - .add('with os', () => { + .addDecorator(withTheme) + .add('ExceptionItem/with os', () => { const payload = getExceptionItemMock(); payload.description = ''; payload.comment = []; @@ -27,15 +32,13 @@ storiesOf('ExceptionItem', module) ]; return ( - ({ eui: euiLightVars, darkMode: false })}> - {}} - handleEdit={() => {}} - /> - + {}} + onEditException={() => {}} + /> ); }) .add('with description', () => { @@ -52,15 +55,13 @@ storiesOf('ExceptionItem', module) ]; return ( - ({ eui: euiLightVars, darkMode: false })}> - {}} - handleEdit={() => {}} - /> - + {}} + onEditException={() => {}} + /> ); }) .add('with comments', () => { @@ -77,15 +78,13 @@ storiesOf('ExceptionItem', module) ]; return ( - ({ eui: euiLightVars, darkMode: false })}> - {}} - handleEdit={() => {}} - /> - + {}} + onEditException={() => {}} + /> ); }) .add('with nested entries', () => { @@ -95,29 +94,25 @@ storiesOf('ExceptionItem', module) payload.comment = []; return ( - ({ eui: euiLightVars, darkMode: false })}> - {}} - handleEdit={() => {}} - /> - + {}} + onEditException={() => {}} + /> ); }) .add('with everything', () => { const payload = getExceptionItemMock(); return ( - ({ eui: euiLightVars, darkMode: false })}> - {}} - handleEdit={() => {}} - /> - + {}} + onEditException={() => {}} + /> ); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx index 7698605588e768..2893c7dc961f26 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx @@ -10,7 +10,7 @@ import moment from 'moment-timezone'; import { getOperatorType, getExceptionOperatorSelect, - determineIfIsNested, + isEntryNested, getFormattedEntries, formatEntry, getOperatingSystems, @@ -159,21 +159,21 @@ describe('Exception helpers', () => { }); }); - describe('#determineIfIsNested', () => { + describe('#isEntryNested', () => { test('it returns true if type NestedExceptionEntry', () => { const payload: NestedExceptionEntry = { field: 'actingProcess.file.signer', type: 'nested', entries: [], }; - const result = determineIfIsNested(payload); + const result = isEntryNested(payload); expect(result).toBeTruthy(); }); test('it returns false if NOT type NestedExceptionEntry', () => { const payload = getExceptionItemEntryMock(); - const result = determineIfIsNested(payload); + const result = isEntryNested(payload); expect(result).toBeFalsy(); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx index bd22de636bf6c8..155c8a3e2926c0 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx @@ -56,7 +56,7 @@ export const getExceptionOperatorSelect = (entry: ExceptionEntry): OperatorOptio return foundOperator ?? isOperator; }; -export const determineIfIsNested = ( +export const isEntryNested = ( tbd: ExceptionEntry | NestedExceptionEntry ): tbd is NestedExceptionEntry => { if (tbd.type === 'nested') { @@ -75,7 +75,7 @@ export const getFormattedEntries = ( entries: Array ): FormattedEntry[] => { const formattedEntries = entries.map((entry) => { - if (determineIfIsNested(entry)) { + if (isEntryNested(entry)) { const parent = { fieldName: entry.field, operator: null, value: null, isNested: false }; return entry.entries.reduce( (acc, nestedEntry) => { diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.tsx index 6f418808b239a6..12287f7cd0fa9b 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_details.tsx @@ -4,28 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexItem, EuiFlexGroup, EuiDescriptionList, EuiButtonEmpty } from '@elastic/eui'; -import React, { useMemo } from 'react'; +import { + EuiFlexItem, + EuiFlexGroup, + EuiDescriptionList, + EuiButtonEmpty, + EuiDescriptionListTitle, + EuiDescriptionListDescription, +} from '@elastic/eui'; +import React, { useMemo, Fragment } from 'react'; import styled, { css } from 'styled-components'; -import { transparentize } from 'polished'; -import { ExceptionListItemSchema } from '../../types'; +import { DescriptionListItem, ExceptionListItemSchema } from '../../types'; import { getDescriptionListContent } from '../../helpers'; import * as i18n from '../../translations'; const StyledExceptionDetails = styled(EuiFlexItem)` ${({ theme }) => css` - background-color: ${transparentize(0.95, theme.eui.euiColorPrimary)}; + background-color: ${theme.eui.euiColorLightestShade}; padding: ${theme.eui.euiSize}; + `} +`; - .euiDescriptionList__title.listTitle--width { - width: 40%; - } +const MyDescriptionListTitle = styled(EuiDescriptionListTitle)` + width: 40%; +`; - .euiDescriptionList__description.listDescription--width { - width: 60%; - } - `} +const MyDescriptionListDescription = styled(EuiDescriptionListDescription)` + width: 60%; `; const ExceptionDetailsComponent = ({ @@ -37,7 +43,10 @@ const ExceptionDetailsComponent = ({ exceptionItem: ExceptionListItemSchema; onCommentsClick: () => void; }): JSX.Element => { - const descriptionList = useMemo(() => getDescriptionListContent(exceptionItem), [exceptionItem]); + const descriptionListItems = useMemo( + (): DescriptionListItem[] => getDescriptionListContent(exceptionItem), + [exceptionItem] + ); const commentsSection = useMemo((): JSX.Element => { // TODO: return back to exceptionItem.comments once updated @@ -62,14 +71,14 @@ const ExceptionDetailsComponent = ({ - + + {descriptionListItems.map((item) => ( + + {item.title} + {item.description} + + ))} + {commentsSection} diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.test.tsx index 10f11231ace017..b2408a654b1c68 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.test.tsx @@ -20,8 +20,8 @@ describe('ExceptionEntries', () => { ); @@ -35,8 +35,8 @@ describe('ExceptionEntries', () => { ); @@ -45,39 +45,39 @@ describe('ExceptionEntries', () => { }); test('it invokes "handlEdit" when edit button clicked', () => { - const mockHandleEdit = jest.fn(); + const mockOnEdit = jest.fn(); const wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> ); const editBtn = wrapper.find('[data-test-subj="exceptionsViewerEditBtn"] button').at(0); editBtn.simulate('click'); - expect(mockHandleEdit).toHaveBeenCalledTimes(1); + expect(mockOnEdit).toHaveBeenCalledTimes(1); }); - test('it invokes "handleDelete" when delete button clicked', () => { - const mockHandleDelete = jest.fn(); + test('it invokes "onDelete" when delete button clicked', () => { + const mockOnDelete = jest.fn(); const wrapper = mount( ({ eui: euiLightVars, darkMode: false })}> ); const deleteBtn = wrapper.find('[data-test-subj="exceptionsViewerDeleteBtn"] button').at(0); deleteBtn.simulate('click'); - expect(mockHandleDelete).toHaveBeenCalledTimes(1); + expect(mockOnDelete).toHaveBeenCalledTimes(1); }); test('it renders nested entry', () => { @@ -90,8 +90,8 @@ describe('ExceptionEntries', () => { ); @@ -132,8 +132,8 @@ describe('ExceptionEntries', () => { ); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.tsx index 6f29875784e613..8c758e3b84f42a 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/exception_entries.tsx @@ -11,6 +11,7 @@ import { EuiFlexGroup, EuiButton, EuiTableFieldDataColumnType, + EuiHideFor, } from '@elastic/eui'; import React, { useMemo } from 'react'; import styled, { css } from 'styled-components'; @@ -48,15 +49,15 @@ const AndOrBadgeContainer = styled(EuiFlexItem)` interface ExceptionEntriesComponentProps { entries: FormattedEntry[]; disableDelete: boolean; - handleDelete: () => void; - handleEdit: () => void; + onDelete: () => void; + onEdit: () => void; } const ExceptionEntriesComponent = ({ entries, disableDelete, - handleDelete, - handleEdit, + onDelete, + onEdit, }: ExceptionEntriesComponentProps): JSX.Element => { const columns = useMemo( (): Array> => [ @@ -65,6 +66,7 @@ const ExceptionEntriesComponent = ({ name: 'Field', sortable: false, truncateText: true, + textOnly: true, 'data-test-subj': 'exceptionFieldNameCell', width: '30%', render: (value: string | null, data: FormattedEntry) => { @@ -121,9 +123,15 @@ const ExceptionEntriesComponent = ({ {entries.length > 1 && ( - - - + + + + + )} - + @@ -153,7 +161,7 @@ const ExceptionEntriesComponent = ({ diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx index 784fc4336a5ff0..dca3afe4f90691 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx @@ -21,8 +21,8 @@ describe('ExceptionItem', () => { @@ -32,8 +32,8 @@ describe('ExceptionItem', () => { expect(wrapper.find('ExceptionEntries')).toHaveLength(1); }); - it('it invokes "handleEdit" when edit button clicked', () => { - const mockHandleEdit = jest.fn(); + it('it invokes "onEditException" when edit button clicked', () => { + const mockOnEditException = jest.fn(); const exceptionItem = getExceptionItemMock(); const wrapper = mount( @@ -41,8 +41,8 @@ describe('ExceptionItem', () => { @@ -51,11 +51,11 @@ describe('ExceptionItem', () => { const editBtn = wrapper.find('[data-test-subj="exceptionsViewerEditBtn"] button').at(0); editBtn.simulate('click'); - expect(mockHandleEdit).toHaveBeenCalledTimes(1); + expect(mockOnEditException).toHaveBeenCalledTimes(1); }); - it('it invokes "handleDelete" when delete button clicked', () => { - const mockHandleDelete = jest.fn(); + it('it invokes "onDeleteException" when delete button clicked', () => { + const mockOnDeleteException = jest.fn(); const exceptionItem = getExceptionItemMock(); const wrapper = mount( @@ -63,8 +63,8 @@ describe('ExceptionItem', () => { @@ -73,11 +73,11 @@ describe('ExceptionItem', () => { const editBtn = wrapper.find('[data-test-subj="exceptionsViewerDeleteBtn"] button').at(0); editBtn.simulate('click'); - expect(mockHandleDelete).toHaveBeenCalledTimes(1); + expect(mockOnDeleteException).toHaveBeenCalledTimes(1); }); it('it renders comment accordion closed to begin with', () => { - const mockHandleDelete = jest.fn(); + const mockOnDeleteException = jest.fn(); const exceptionItem = getExceptionItemMock(); const wrapper = mount( @@ -85,8 +85,8 @@ describe('ExceptionItem', () => { @@ -96,7 +96,7 @@ describe('ExceptionItem', () => { }); it('it renders comment accordion open when showComments is true', () => { - const mockHandleDelete = jest.fn(); + const mockOnDeleteException = jest.fn(); const exceptionItem = getExceptionItemMock(); const wrapper = mount( @@ -104,8 +104,8 @@ describe('ExceptionItem', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.tsx index 386ab6f3c3c7cb..ba6ffb109be042 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.tsx @@ -21,26 +21,26 @@ import { getFormattedEntries, getFormattedComments } from '../../helpers'; import { FormattedEntry, ExceptionListItemSchema, ApiProps } from '../../types'; const MyFlexItem = styled(EuiFlexItem)` - &.comments--show { - padding: ${({ theme }) => theme.eui.euiSize}; - border-top: ${({ theme }) => `${theme.eui.euiBorderThin}`} - + &.comments--show { + padding: ${({ theme }) => theme.eui.euiSize}; + border-top: ${({ theme }) => `${theme.eui.euiBorderThin}`}; + } `; interface ExceptionItemProps { loadingItemIds: ApiProps[]; exceptionItem: ExceptionListItemSchema; commentsAccordionId: string; - handleDelete: (arg: ApiProps) => void; - handleEdit: (item: ExceptionListItemSchema) => void; + onDeleteException: (arg: ApiProps) => void; + onEditException: (item: ExceptionListItemSchema) => void; } const ExceptionItemComponent = ({ loadingItemIds, exceptionItem, commentsAccordionId, - handleDelete, - handleEdit, + onDeleteException, + onEditException, }: ExceptionItemProps): JSX.Element => { const [entryItems, setEntryItems] = useState([]); const [showComments, setShowComments] = useState(false); @@ -50,13 +50,13 @@ const ExceptionItemComponent = ({ setEntryItems(formattedEntries); }, [exceptionItem.entries]); - const onDelete = useCallback((): void => { - handleDelete({ id: exceptionItem.id, namespaceType: exceptionItem.namespace_type }); - }, [handleDelete, exceptionItem]); + const handleDelete = useCallback((): void => { + onDeleteException({ id: exceptionItem.id, namespaceType: exceptionItem.namespace_type }); + }, [onDeleteException, exceptionItem]); - const onEdit = useCallback((): void => { - handleEdit(exceptionItem); - }, [handleEdit, exceptionItem]); + const handleEdit = useCallback((): void => { + onEditException(exceptionItem); + }, [onEditException, exceptionItem]); const onCommentsClick = useCallback((): void => { setShowComments(!showComments); @@ -85,8 +85,8 @@ const ExceptionItemComponent = ({ diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx index ff52e395c3b1e8..3cf59c7dda023e 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx @@ -195,7 +195,7 @@ const ExceptionsViewerComponent = ({ [setIsModalOpen] ); - const onEditExceptionItem = useCallback( + const handleEditException = useCallback( (exception: ExceptionListItemSchema): void => { // TODO: Added this just for testing. Update // modal state logic as needed once ready @@ -234,7 +234,7 @@ const ExceptionsViewerComponent = ({ [dispatch] ); - const onDeleteException = useCallback( + const handleDeleteException = useCallback( ({ id, namespaceType }: ApiProps) => { deleteExceptionItem({ id, @@ -395,8 +395,8 @@ const ExceptionsViewerComponent = ({ loadingItemIds={loadingItemIds} commentsAccordionId={commentsAccordionId} exceptionItem={exception} - handleDelete={onDeleteException} - handleEdit={onEditExceptionItem} + onDeleteException={handleDeleteException} + onEditException={handleEditException} /> ))} diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts index 628d56ebb647cc..dc4a8ae78bf46d 100644 --- a/x-pack/plugins/security_solution/public/graphql/types.ts +++ b/x-pack/plugins/security_solution/public/graphql/types.ts @@ -4340,6 +4340,8 @@ export namespace GetAllTimeline { pinnedEventIds: Maybe; + status: Maybe; + title: Maybe; timelineType: Maybe; diff --git a/x-pack/plugins/security_solution/scripts/storybook.js b/x-pack/plugins/security_solution/scripts/storybook.js index 5f06f2a4ebb12b..cd4d16d89c48d0 100644 --- a/x-pack/plugins/security_solution/scripts/storybook.js +++ b/x-pack/plugins/security_solution/scripts/storybook.js @@ -9,5 +9,5 @@ import { join } from 'path'; // eslint-disable-next-line require('@kbn/storybook').runStorybookCli({ name: 'siem', - storyGlobs: [join(__dirname, '..', 'public', '**', 'components', '**', '*.stories.tsx')], + storyGlobs: [join(__dirname, '..', 'public', '**', '*.stories.tsx')], });