From 8316efcef537fb25426ff5478fe8749a04648144 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Mon, 17 Feb 2020 15:33:17 +0200 Subject: [PATCH 01/21] Create edit node component --- .../pages/case/components/case_view/index.tsx | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 4f43a6edeeac6..12c4e052f2186 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -16,6 +16,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, + EuiFieldText, } from '@elastic/eui'; import styled, { css } from 'styled-components'; @@ -63,6 +64,53 @@ interface CasesProps { initialData: Case; isLoading: boolean; } +interface IconAction { + 'aria-label': string; + iconType: string; + onChange: (a: string) => void; + onClick: (b: boolean) => void; + onSubmit: () => void; +} + +interface EditNodeComponentProps { + iconAction: IconAction; + isLoading: boolean; + title: string | React.ReactNode; + isEditTitle?: boolean; +} + +const EditNodeComponent = ({ + iconAction, + isLoading, + title, + isEditTitle, +}: EditNodeComponentProps) => { + return ( + isEditTitle && ( + + + iconAction.onChange(e.target.value)} value={`${title}`} /> + + + + + {i18n.SUBMIT} + + + + iconAction.onClick(false)}>{i18n.CANCEL} + + + + + ) + ); +}; export const Cases = React.memo(({ caseId, initialData, isLoading }) => { const [{ data }, dispatchUpdateCaseProperty] = useUpdateCase(caseId, initialData); From 34632817ff91ea035aac8f21bc8c38604c223579 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Mon, 17 Feb 2020 15:33:49 +0200 Subject: [PATCH 02/21] Accept EditNodeComponent --- .../public/components/header_page/index.tsx | 97 +++++++++++++------ 1 file changed, 67 insertions(+), 30 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx index d694aad3dd896..4ed89f1e517d9 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx @@ -11,6 +11,7 @@ import { EuiFlexItem, EuiProgress, EuiTitle, + EuiButtonIcon, } from '@elastic/eui'; import React from 'react'; import styled, { css } from 'styled-components'; @@ -69,6 +70,13 @@ const StyledEuiBetaBadge = styled(EuiBetaBadge)` StyledEuiBetaBadge.displayName = 'StyledEuiBetaBadge'; +const StyledEuiButtonIcon = styled(EuiButtonIcon)` + ${({ theme }) => css` + margin-left: ${theme.eui.euiSize}; + `} +`; +StyledEuiButtonIcon.displayName = 'StyledEuiButtonIcon'; + interface BackOptions { href: LinkIconProps['href']; text: LinkIconProps['children']; @@ -85,6 +93,14 @@ interface DraggableArguments { value: string; } +interface IconAction { + 'aria-label': string; + iconType: string; + onChange: (a: string) => void; + onClick: (b: boolean) => void; + onSubmit: () => void; +} + export interface HeaderPageProps extends HeaderProps { backOptions?: BackOptions; badgeOptions?: BadgeOptions; @@ -93,6 +109,9 @@ export interface HeaderPageProps extends HeaderProps { subtitle?: SubtitleProps['items']; subtitle2?: SubtitleProps['items']; title: string | React.ReactNode; + iconAction?: IconAction; + EditTitleNode?: React.ReactNode; + isEditTitle?: boolean; } const HeaderPageComponent: React.FC = ({ @@ -105,6 +124,9 @@ const HeaderPageComponent: React.FC = ({ subtitle, subtitle2, title, + isEditTitle, + EditTitleNode, + iconAction, ...rest }) => (
@@ -117,36 +139,51 @@ const HeaderPageComponent: React.FC = ({ )} - - -

- {!draggableArguments ? ( - title - ) : ( - - )} - {badgeOptions && ( - <> - {' '} - {badgeOptions.beta ? ( - - ) : ( - {badgeOptions.text} - )} - - )} -

-
- + {EditTitleNode && isEditTitle && ( + + )} + {(!EditTitleNode || !isEditTitle) && ( + +

+ {!draggableArguments ? ( + title + ) : ( + + )} + {badgeOptions && ( + <> + {' '} + {badgeOptions.beta ? ( + + ) : ( + {badgeOptions.text} + )} + + )} + {EditTitleNode && iconAction && !isEditTitle && ( + iconAction.onClick(true)} + /> + )} +

+
+ )} {subtitle && } {subtitle2 && } {border && isLoading && } From 5186d4e03d4c7c5507ad895c9c4a1c8e0d10146f Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Mon, 17 Feb 2020 15:34:13 +0200 Subject: [PATCH 03/21] Switch to old header --- .../siem/public/pages/case/components/case_view/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 12c4e052f2186..79ce16710e979 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -25,7 +25,7 @@ import { DescriptionMarkdown } from '../description_md_editor'; import { Case } from '../../../../containers/case/types'; import { FormattedRelativePreferenceDate } from '../../../../components/formatted_date'; import { getCaseUrl } from '../../../../components/link_to'; -import { HeaderPage } from '../../../../components/header_page_new'; +import { HeaderPage } from '../../../../components/header_page'; import { Markdown } from '../../../../components/markdown'; import { PropertyActions } from '../property_actions'; import { TagList } from '../tag_list'; @@ -274,6 +274,7 @@ export const Cases = React.memo(({ caseId, initialData, isLoading }) onClick: isEdit => setIsEditTitle(isEdit), }} isEditTitle={isEditTitle} + EditTitleNode={EditNodeComponent} title={title} > From 4b7ce66db8453361c53885245129874f13cc0c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopycin=CC=81ski?= Date: Mon, 17 Feb 2020 16:16:09 +0100 Subject: [PATCH 04/21] test --- .../public/components/header_page/index.tsx | 103 ++-------------- .../public/components/header_page/title.tsx | 62 ++++++++++ .../public/components/header_page/types.ts | 18 +++ .../pages/case/components/case_view/index.tsx | 111 ++++++++++-------- 4 files changed, 156 insertions(+), 138 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/components/header_page/title.tsx create mode 100644 x-pack/legacy/plugins/siem/public/components/header_page/types.ts diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx index 4ed89f1e517d9..f88ffc3f3c6c4 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx @@ -4,21 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiBetaBadge, - EuiBadge, - EuiFlexGroup, - EuiFlexItem, - EuiProgress, - EuiTitle, - EuiButtonIcon, -} from '@elastic/eui'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiProgress } from '@elastic/eui'; import React from 'react'; import styled, { css } from 'styled-components'; -import { DefaultDraggable } from '../draggables'; import { LinkIcon, LinkIconProps } from '../link_icon'; import { Subtitle, SubtitleProps } from '../subtitle'; +import { Title } from './title'; +import { DraggableArguments, BadgeOptions, TitleProp } from './types'; interface HeaderProps { border?: boolean; @@ -64,43 +57,11 @@ const Badge = styled(EuiBadge)` `; Badge.displayName = 'Badge'; -const StyledEuiBetaBadge = styled(EuiBetaBadge)` - vertical-align: middle; -`; - -StyledEuiBetaBadge.displayName = 'StyledEuiBetaBadge'; - -const StyledEuiButtonIcon = styled(EuiButtonIcon)` - ${({ theme }) => css` - margin-left: ${theme.eui.euiSize}; - `} -`; -StyledEuiButtonIcon.displayName = 'StyledEuiButtonIcon'; - interface BackOptions { href: LinkIconProps['href']; text: LinkIconProps['children']; } -interface BadgeOptions { - beta?: boolean; - text: string; - tooltip?: string; -} - -interface DraggableArguments { - field: string; - value: string; -} - -interface IconAction { - 'aria-label': string; - iconType: string; - onChange: (a: string) => void; - onClick: (b: boolean) => void; - onSubmit: () => void; -} - export interface HeaderPageProps extends HeaderProps { backOptions?: BackOptions; badgeOptions?: BadgeOptions; @@ -108,10 +69,8 @@ export interface HeaderPageProps extends HeaderProps { draggableArguments?: DraggableArguments; subtitle?: SubtitleProps['items']; subtitle2?: SubtitleProps['items']; - title: string | React.ReactNode; - iconAction?: IconAction; - EditTitleNode?: React.ReactNode; - isEditTitle?: boolean; + title: TitleProp; + titleNode?: React.ReactElement; } const HeaderPageComponent: React.FC = ({ @@ -124,9 +83,7 @@ const HeaderPageComponent: React.FC = ({ subtitle, subtitle2, title, - isEditTitle, - EditTitleNode, - iconAction, + titleNode, ...rest }) => (
@@ -139,51 +96,15 @@ const HeaderPageComponent: React.FC = ({ )} - {EditTitleNode && isEditTitle && ( - )} - {(!EditTitleNode || !isEditTitle) && ( - -

- {!draggableArguments ? ( - title - ) : ( - - )} - {badgeOptions && ( - <> - {' '} - {badgeOptions.beta ? ( - - ) : ( - {badgeOptions.text} - )} - - )} - {EditTitleNode && iconAction && !isEditTitle && ( - iconAction.onClick(true)} - /> - )} -

-
- )} + {subtitle && } {subtitle2 && } {border && isLoading && } diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/title.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/title.tsx new file mode 100644 index 0000000000000..a1f3cfd857148 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_page/title.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 React from 'react'; +import { EuiBetaBadge, EuiBadge, EuiTitle } from '@elastic/eui'; +import styled from 'styled-components'; + +import { DraggableArguments, BadgeOptions, TitleProp } from './types'; +import { DefaultDraggable } from '../draggables'; + +const StyledEuiBetaBadge = styled(EuiBetaBadge)` + vertical-align: middle; +`; + +StyledEuiBetaBadge.displayName = 'StyledEuiBetaBadge'; + +const Badge = styled(EuiBadge)` + letter-spacing: 0; +`; +Badge.displayName = 'Badge'; + +interface Props { + badgeOptions?: BadgeOptions; + title: TitleProp; + draggableArguments?: DraggableArguments; +} + +const TitleComponent: React.FC = ({ draggableArguments, title, badgeOptions }) => ( + +

+ {!draggableArguments ? ( + title + ) : ( + + )} + {badgeOptions && ( + <> + {' '} + {badgeOptions.beta ? ( + + ) : ( + {badgeOptions.text} + )} + + )} +

+
+); + +export const Title = React.memo(TitleComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/types.ts b/x-pack/legacy/plugins/siem/public/components/header_page/types.ts new file mode 100644 index 0000000000000..3c16af83585e9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_page/types.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +export type TitleProp = string | React.ReactNode; + +export interface DraggableArguments { + field: string; + value: string; +} + +export interface BadgeOptions { + beta?: boolean; + text: string; + tooltip?: string; +} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 79ce16710e979..0486d6eb182d5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -17,6 +17,7 @@ import { EuiFlexItem, EuiLoadingSpinner, EuiFieldText, + EuiButtonIcon, } from '@elastic/eui'; import styled, { css } from 'styled-components'; @@ -26,6 +27,7 @@ import { Case } from '../../../../containers/case/types'; import { FormattedRelativePreferenceDate } from '../../../../components/formatted_date'; import { getCaseUrl } from '../../../../components/link_to'; import { HeaderPage } from '../../../../components/header_page'; +import { Title } from '../../../../components/header_page/title'; import { Markdown } from '../../../../components/markdown'; import { PropertyActions } from '../property_actions'; import { TagList } from '../tag_list'; @@ -51,6 +53,7 @@ const MyDescriptionList = styled(EuiDescriptionList)` const MyWrapper = styled(WrapperPage)` padding-bottom: 0; `; + const BackgroundWrapper = styled.div` ${({ theme }) => css` background-color: ${theme.eui.euiColorEmptyShade}; @@ -59,58 +62,54 @@ const BackgroundWrapper = styled.div` `} `; +const StyledEuiButtonIcon = styled(EuiButtonIcon)` + ${({ theme }) => css` + margin-left: ${theme.eui.euiSize}; + `} +`; + +StyledEuiButtonIcon.displayName = 'StyledEuiButtonIcon'; + interface CasesProps { caseId: string; initialData: Case; isLoading: boolean; } -interface IconAction { - 'aria-label': string; - iconType: string; - onChange: (a: string) => void; - onClick: (b: boolean) => void; - onSubmit: () => void; -} interface EditNodeComponentProps { - iconAction: IconAction; isLoading: boolean; title: string | React.ReactNode; isEditTitle?: boolean; + iconType: string; + onChange: (a: string) => void; + onClick: (b: boolean) => void; + onSubmit: () => void; } -const EditNodeComponent = ({ - iconAction, +const EditNodeComponent: React.FC = ({ + onChange, + onClick, + onSubmit, isLoading, title, - isEditTitle, -}: EditNodeComponentProps) => { - return ( - isEditTitle && ( - - - iconAction.onChange(e.target.value)} value={`${title}`} /> - - - - - {i18n.SUBMIT} - - - - iconAction.onClick(false)}>{i18n.CANCEL} - - - - - ) - ); -}; +}) => ( + + + onChange(e.target.value)} value={`${title}`} /> + + + + + {i18n.SUBMIT} + + + + onClick(false)}>{i18n.CANCEL} + + + + +); export const Cases = React.memo(({ caseId, initialData, isLoading }) => { const [{ data }, dispatchUpdateCaseProperty] = useUpdateCase(caseId, initialData); @@ -258,6 +257,32 @@ export const Cases = React.memo(({ caseId, initialData, isLoading }) ), }, ]; + + const titleNode = isEditTitle ? ( + setTitle(newTitle)} + onSubmit={() => onUpdateField('title', title)} + onClick={isEdit => setIsEditTitle(isEdit)} + /> + ) : ( + + + + </EuiFlexItem> + <EuiFlexItem grow={false}> + <StyledEuiButtonIcon + aria-label={title} + iconType="pencil" + onClick={() => setIsEditTitle(true)} + /> + </EuiFlexItem> + </EuiFlexGroup> + ); + return ( <> <MyWrapper> @@ -266,15 +291,7 @@ export const Cases = React.memo<CasesProps>(({ caseId, initialData, isLoading }) href: getCaseUrl(), text: i18n.BACK_TO_ALL, }} - iconAction={{ - 'aria-label': title, - iconType: 'pencil', - onChange: newTitle => setTitle(newTitle), - onSubmit: () => onUpdateField('title', title), - onClick: isEdit => setIsEditTitle(isEdit), - }} - isEditTitle={isEditTitle} - EditTitleNode={EditNodeComponent} + titleNode={titleNode} title={title} > <EuiFlexGroup gutterSize="l" justifyContent="flexEnd"> From 8ab31e7a6b922dbd772a5662f623b83e7a326dcd Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Mon, 17 Feb 2020 18:51:37 +0200 Subject: [PATCH 05/21] Remove iconType --- .../siem/public/pages/case/components/case_view/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 0486d6eb182d5..c63763206a7d5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -80,7 +80,6 @@ interface EditNodeComponentProps { isLoading: boolean; title: string | React.ReactNode; isEditTitle?: boolean; - iconType: string; onChange: (a: string) => void; onClick: (b: boolean) => void; onSubmit: () => void; @@ -263,7 +262,6 @@ export const Cases = React.memo<CasesProps>(({ caseId, initialData, isLoading }) isLoading={isLoading} title={title} isEditTitle={isEditTitle} - iconType="pencil" onChange={newTitle => setTitle(newTitle)} onSubmit={() => onUpdateField('title', title)} onClick={isEdit => setIsEditTitle(isEdit)} From 5102907475c33b640e3f7a9f83e0d0bace10db33 Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Mon, 17 Feb 2020 18:52:11 +0200 Subject: [PATCH 06/21] Remove isEditTitle --- .../siem/public/pages/case/components/case_view/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index c63763206a7d5..7c25f53c94ef2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -261,7 +261,6 @@ export const Cases = React.memo<CasesProps>(({ caseId, initialData, isLoading }) <EditNodeComponent isLoading={isLoading} title={title} - isEditTitle={isEditTitle} onChange={newTitle => setTitle(newTitle)} onSubmit={() => onUpdateField('title', title)} onClick={isEdit => setIsEditTitle(isEdit)} From 658440b89f1ff3e2e99446ae5b460c4110fecf9d Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Mon, 17 Feb 2020 18:58:49 +0200 Subject: [PATCH 07/21] Move translations --- .../pages/case/components/case_view/translations.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts index f45c52533d2e7..bacd42490937a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts @@ -43,3 +43,11 @@ export const STATUS = i18n.translate('xpack.siem.case.caseView.statusLabel', { export const CASE_OPENED = i18n.translate('xpack.siem.case.caseView.caseOpened', { defaultMessage: 'Case opened', }); + +export const SUBMIT = i18n.translate('xpack.siem.case.casePage.title.submit', { + defaultMessage: 'Submit', +}); + +export const CANCEL = i18n.translate('xpack.siem.case.casePage.title.cancel', { + defaultMessage: 'Cancel', +}); From bdf028011e9faeff0edc4380f7ea0a54fcafb075 Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Mon, 17 Feb 2020 19:00:06 +0200 Subject: [PATCH 08/21] Delete header_page_new component --- .../__snapshots__/index.test.tsx.snap | 47 ---- .../components/header_page_new/index.test.tsx | 224 ------------------ .../components/header_page_new/index.tsx | 220 ----------------- .../header_page_new/translations.ts | 15 -- 4 files changed, 506 deletions(-) delete mode 100644 x-pack/legacy/plugins/siem/public/components/header_page_new/__snapshots__/index.test.tsx.snap delete mode 100644 x-pack/legacy/plugins/siem/public/components/header_page_new/index.test.tsx delete mode 100644 x-pack/legacy/plugins/siem/public/components/header_page_new/index.tsx delete mode 100644 x-pack/legacy/plugins/siem/public/components/header_page_new/translations.ts diff --git a/x-pack/legacy/plugins/siem/public/components/header_page_new/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_page_new/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 1b792503cf1c6..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/header_page_new/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,47 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`HeaderPage it renders 1`] = ` -<Header - border={true} -> - <EuiFlexGroup - alignItems="center" - justifyContent="spaceBetween" - > - <FlexItem - grow={false} - > - <EuiTitle - size="l" - > - <h1 - data-test-subj="header-page-title" - > - Test title - - <StyledEuiBetaBadge - label="Beta" - tooltipContent="Test tooltip" - tooltipPosition="bottom" - /> - </h1> - </EuiTitle> - <Subtitle - data-test-subj="header-page-subtitle" - items="Test subtitle" - /> - <Subtitle - data-test-subj="header-page-subtitle-2" - items="Test subtitle 2" - /> - </FlexItem> - <FlexItem - data-test-subj="header-page-supplements" - > - <p> - Test supplement - </p> - </FlexItem> - </EuiFlexGroup> -</Header> -`; diff --git a/x-pack/legacy/plugins/siem/public/components/header_page_new/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_page_new/index.test.tsx deleted file mode 100644 index 83a70fd90d82b..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/header_page_new/index.test.tsx +++ /dev/null @@ -1,224 +0,0 @@ -/* - * 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 euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; -import { shallow } from 'enzyme'; -import React from 'react'; - -import { TestProviders } from '../../mock'; -import { HeaderPage } from './index'; -import { useMountAppended } from '../../utils/use_mount_appended'; - -describe('HeaderPage', () => { - const mount = useMountAppended(); - - test('it renders', () => { - const wrapper = shallow( - <HeaderPage - badgeOptions={{ beta: true, text: 'Beta', tooltip: 'Test tooltip' }} - border - subtitle="Test subtitle" - subtitle2="Test subtitle 2" - title="Test title" - > - <p>{'Test supplement'}</p> - </HeaderPage> - ); - - expect(wrapper).toMatchSnapshot(); - }); - - test('it renders the title', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage title="Test title" /> - </TestProviders> - ); - - expect( - wrapper - .find('[data-test-subj="header-page-title"]') - .first() - .exists() - ).toBe(true); - }); - - test('it renders the back link when provided', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage backOptions={{ href: '#', text: 'Test link' }} title="Test title" /> - </TestProviders> - ); - - expect( - wrapper - .find('.siemHeaderPage__linkBack') - .first() - .exists() - ).toBe(true); - }); - - test('it DOES NOT render the back link when not provided', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage title="Test title" /> - </TestProviders> - ); - - expect( - wrapper - .find('.siemHeaderPage__linkBack') - .first() - .exists() - ).toBe(false); - }); - - test('it renders the first subtitle when provided', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage subtitle="Test subtitle" title="Test title" /> - </TestProviders> - ); - - expect( - wrapper - .find('[data-test-subj="header-page-subtitle"]') - .first() - .exists() - ).toBe(true); - }); - - test('it DOES NOT render the first subtitle when not provided', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage title="Test title" /> - </TestProviders> - ); - - expect( - wrapper - .find('[data-test-subj="header-section-subtitle"]') - .first() - .exists() - ).toBe(false); - }); - - test('it renders the second subtitle when provided', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage subtitle2="Test subtitle 2" title="Test title" /> - </TestProviders> - ); - - expect( - wrapper - .find('[data-test-subj="header-page-subtitle-2"]') - .first() - .exists() - ).toBe(true); - }); - - test('it DOES NOT render the second subtitle when not provided', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage title="Test title" /> - </TestProviders> - ); - - expect( - wrapper - .find('[data-test-subj="header-section-subtitle-2"]') - .first() - .exists() - ).toBe(false); - }); - - test('it renders supplements when children provided', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage title="Test title"> - <p>{'Test supplement'}</p> - </HeaderPage> - </TestProviders> - ); - - expect( - wrapper - .find('[data-test-subj="header-page-supplements"]') - .first() - .exists() - ).toBe(true); - }); - - test('it DOES NOT render supplements when children not provided', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage title="Test title" /> - </TestProviders> - ); - - expect( - wrapper - .find('[data-test-subj="header-page-supplements"]') - .first() - .exists() - ).toBe(false); - }); - - test('it applies border styles when border is true', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage border title="Test title" /> - </TestProviders> - ); - const siemHeaderPage = wrapper.find('.siemHeaderPage').first(); - - expect(siemHeaderPage).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); - expect(siemHeaderPage).toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l); - }); - - test('it DOES NOT apply border styles when border is false', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage title="Test title" /> - </TestProviders> - ); - const siemHeaderPage = wrapper.find('.siemHeaderPage').first(); - - expect(siemHeaderPage).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); - expect(siemHeaderPage).not.toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l); - }); - - test('it renders as a draggable when arguments provided', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage draggableArguments={{ field: 'neat', value: 'cool' }} title="Test title" /> - </TestProviders> - ); - - expect( - wrapper - .find('[data-test-subj="header-page-draggable"]') - .first() - .exists() - ).toBe(true); - }); - - test('it DOES NOT render as a draggable when arguments not provided', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage title="Test title" /> - </TestProviders> - ); - - expect( - wrapper - .find('[data-test-subj="header-page-draggable"]') - .first() - .exists() - ).toBe(false); - }); -}); diff --git a/x-pack/legacy/plugins/siem/public/components/header_page_new/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_page_new/index.tsx deleted file mode 100644 index 7e486c78fb9b9..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/header_page_new/index.tsx +++ /dev/null @@ -1,220 +0,0 @@ -/* - * 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, - EuiBetaBadge, - EuiButton, - EuiButtonEmpty, - EuiButtonIcon, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiProgress, - EuiTitle, -} from '@elastic/eui'; -import React from 'react'; -import styled, { css } from 'styled-components'; - -import { DefaultDraggable } from '../draggables'; -import { LinkIcon, LinkIconProps } from '../link_icon'; -import { Subtitle, SubtitleProps } from '../subtitle'; -import * as i18n from './translations'; - -interface HeaderProps { - border?: boolean; - isLoading?: boolean; -} - -const Header = styled.header.attrs({ - className: 'siemHeaderPage', -})<HeaderProps>` - ${({ border, theme }) => css` - margin-bottom: ${theme.eui.euiSizeL}; - - ${border && - css` - border-bottom: ${theme.eui.euiBorderThin}; - padding-bottom: ${theme.eui.paddingSizes.l}; - .euiProgress { - top: ${theme.eui.paddingSizes.l}; - } - `} - `} -`; -Header.displayName = 'Header'; - -const FlexItem = styled(EuiFlexItem)` - display: block; -`; -FlexItem.displayName = 'FlexItem'; - -const LinkBack = styled.div.attrs({ - className: 'siemHeaderPage__linkBack', -})` - ${({ theme }) => css` - font-size: ${theme.eui.euiFontSizeXS}; - line-height: ${theme.eui.euiLineHeight}; - margin-bottom: ${theme.eui.euiSizeS}; - `} -`; -LinkBack.displayName = 'LinkBack'; - -const Badge = styled(EuiBadge)` - letter-spacing: 0; -`; -Badge.displayName = 'Badge'; - -const StyledEuiBetaBadge = styled(EuiBetaBadge)` - vertical-align: middle; -`; - -StyledEuiBetaBadge.displayName = 'StyledEuiBetaBadge'; - -const StyledEuiButtonIcon = styled(EuiButtonIcon)` - ${({ theme }) => css` - margin-left: ${theme.eui.euiSize}; - `} -`; - -StyledEuiButtonIcon.displayName = 'StyledEuiButtonIcon'; - -interface BackOptions { - href: LinkIconProps['href']; - text: LinkIconProps['children']; -} - -interface BadgeOptions { - beta?: boolean; - text: string; - tooltip?: string; -} - -interface DraggableArguments { - field: string; - value: string; -} -interface IconAction { - 'aria-label': string; - iconType: string; - onChange: (a: string) => void; - onClick: (b: boolean) => void; - onSubmit: () => void; -} - -export interface HeaderPageProps extends HeaderProps { - backOptions?: BackOptions; - badgeOptions?: BadgeOptions; - children?: React.ReactNode; - draggableArguments?: DraggableArguments; - isEditTitle?: boolean; - iconAction?: IconAction; - subtitle2?: SubtitleProps['items']; - subtitle?: SubtitleProps['items']; - title: string | React.ReactNode; -} - -const HeaderPageComponent: React.FC<HeaderPageProps> = ({ - backOptions, - badgeOptions, - border, - children, - draggableArguments, - isEditTitle, - iconAction, - isLoading, - subtitle, - subtitle2, - title, - ...rest -}) => ( - <Header border={border} {...rest}> - <EuiFlexGroup alignItems="center" justifyContent="spaceBetween"> - <FlexItem grow={false}> - {backOptions && ( - <LinkBack> - <LinkIcon href={backOptions.href} iconType="arrowLeft"> - {backOptions.text} - </LinkIcon> - </LinkBack> - )} - - {isEditTitle && iconAction ? ( - <EuiFlexGroup alignItems="center" gutterSize="m" justifyContent="spaceBetween"> - <EuiFlexItem grow={false}> - <EuiFieldText - onChange={e => iconAction.onChange(e.target.value)} - value={`${title}`} - /> - </EuiFlexItem> - <EuiFlexGroup gutterSize="none" responsive={false} wrap={true}> - <EuiFlexItem grow={false}> - <EuiButton - fill - isDisabled={isLoading} - isLoading={isLoading} - onClick={iconAction.onSubmit} - > - {i18n.SUBMIT} - </EuiButton> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiButtonEmpty onClick={() => iconAction.onClick(false)}> - {i18n.CANCEL} - </EuiButtonEmpty> - </EuiFlexItem> - </EuiFlexGroup> - <EuiFlexItem /> - </EuiFlexGroup> - ) : ( - <EuiTitle size="l"> - <h1 data-test-subj="header-page-title"> - {!draggableArguments ? ( - title - ) : ( - <DefaultDraggable - data-test-subj="header-page-draggable" - id={`header-page-draggable-${draggableArguments.field}-${draggableArguments.value}`} - field={draggableArguments.field} - value={`${draggableArguments.value}`} - /> - )} - {badgeOptions && ( - <> - {' '} - {badgeOptions.beta ? ( - <StyledEuiBetaBadge - label={badgeOptions.text} - tooltipContent={badgeOptions.tooltip} - tooltipPosition="bottom" - /> - ) : ( - <Badge color="hollow">{badgeOptions.text}</Badge> - )} - </> - )} - {iconAction && ( - <StyledEuiButtonIcon - aria-label={iconAction['aria-label']} - iconType={iconAction.iconType} - onClick={() => iconAction.onClick(true)} - /> - )} - </h1> - </EuiTitle> - )} - - {subtitle && <Subtitle data-test-subj="header-page-subtitle" items={subtitle} />} - {subtitle2 && <Subtitle data-test-subj="header-page-subtitle-2" items={subtitle2} />} - {border && isLoading && <EuiProgress size="xs" color="accent" />} - </FlexItem> - - {children && <FlexItem data-test-subj="header-page-supplements">{children}</FlexItem>} - </EuiFlexGroup> - </Header> -); - -export const HeaderPage = React.memo(HeaderPageComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/header_page_new/translations.ts b/x-pack/legacy/plugins/siem/public/components/header_page_new/translations.ts deleted file mode 100644 index 57b2cda0b0b01..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/header_page_new/translations.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const SUBMIT = i18n.translate('xpack.siem.case.casePage.title.submit', { - defaultMessage: 'Submit', -}); - -export const CANCEL = i18n.translate('xpack.siem.case.casePage.title.cancel', { - defaultMessage: 'Cancel', -}); From d63ee5154965ae22e1fc2c419a9f85fddd3ad781 Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Mon, 17 Feb 2020 19:11:14 +0200 Subject: [PATCH 09/21] Move editable title component to different folder --- .../components/header_page/editable_title.tsx | 49 +++++++++++++++++++ .../pages/case/components/case_view/index.tsx | 40 ++------------- 2 files changed, 53 insertions(+), 36 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx new file mode 100644 index 0000000000000..9db011469d005 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx @@ -0,0 +1,49 @@ +/* + * 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 { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiFieldText } from '@elastic/eui'; + +interface Props { + submitTitle: string; + cancelTitle: string; + isLoading: boolean; + title: string | React.ReactNode; + isEditTitle?: boolean; + onChange: (a: string) => void; + onClick: (b: boolean) => void; + onSubmit: () => void; +} + +const EditableTitleComponent: React.FC<Props> = ({ + onChange, + onClick, + onSubmit, + isLoading, + title, + submitTitle, + cancelTitle, +}) => ( + <EuiFlexGroup alignItems="center" gutterSize="m" justifyContent="spaceBetween"> + <EuiFlexItem grow={false}> + <EuiFieldText onChange={e => onChange(e.target.value)} value={`${title}`} /> + </EuiFlexItem> + <EuiFlexGroup gutterSize="none" responsive={false} wrap={true}> + <EuiFlexItem grow={false}> + <EuiButton fill isDisabled={isLoading} isLoading={isLoading} onClick={onSubmit}> + {submitTitle} + </EuiButton> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButtonEmpty onClick={() => onClick(false)}>{cancelTitle}</EuiButtonEmpty> + </EuiFlexItem> + </EuiFlexGroup> + <EuiFlexItem /> + </EuiFlexGroup> +); + +export const EditableTitle = React.memo(EditableTitleComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 7c25f53c94ef2..0e64c62eb5c57 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -16,7 +16,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, - EuiFieldText, EuiButtonIcon, } from '@elastic/eui'; @@ -28,6 +27,7 @@ import { FormattedRelativePreferenceDate } from '../../../../components/formatte import { getCaseUrl } from '../../../../components/link_to'; import { HeaderPage } from '../../../../components/header_page'; import { Title } from '../../../../components/header_page/title'; +import { EditableTitle } from '../../../../components/header_page/editable_title'; import { Markdown } from '../../../../components/markdown'; import { PropertyActions } from '../property_actions'; import { TagList } from '../tag_list'; @@ -76,40 +76,6 @@ interface CasesProps { isLoading: boolean; } -interface EditNodeComponentProps { - isLoading: boolean; - title: string | React.ReactNode; - isEditTitle?: boolean; - onChange: (a: string) => void; - onClick: (b: boolean) => void; - onSubmit: () => void; -} - -const EditNodeComponent: React.FC<EditNodeComponentProps> = ({ - onChange, - onClick, - onSubmit, - isLoading, - title, -}) => ( - <EuiFlexGroup alignItems="center" gutterSize="m" justifyContent="spaceBetween"> - <EuiFlexItem grow={false}> - <EuiFieldText onChange={e => onChange(e.target.value)} value={`${title}`} /> - </EuiFlexItem> - <EuiFlexGroup gutterSize="none" responsive={false} wrap={true}> - <EuiFlexItem grow={false}> - <EuiButton fill isDisabled={isLoading} isLoading={isLoading} onClick={onSubmit}> - {i18n.SUBMIT} - </EuiButton> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiButtonEmpty onClick={() => onClick(false)}>{i18n.CANCEL}</EuiButtonEmpty> - </EuiFlexItem> - </EuiFlexGroup> - <EuiFlexItem /> - </EuiFlexGroup> -); - export const Cases = React.memo<CasesProps>(({ caseId, initialData, isLoading }) => { const [{ data }, dispatchUpdateCaseProperty] = useUpdateCase(caseId, initialData); const [isEditDescription, setIsEditDescription] = useState(false); @@ -258,12 +224,14 @@ export const Cases = React.memo<CasesProps>(({ caseId, initialData, isLoading }) ]; const titleNode = isEditTitle ? ( - <EditNodeComponent + <EditableTitle isLoading={isLoading} title={title} onChange={newTitle => setTitle(newTitle)} onSubmit={() => onUpdateField('title', title)} onClick={isEdit => setIsEditTitle(isEdit)} + submitTitle={i18n.SUBMIT} + cancelTitle={i18n.CANCEL} /> ) : ( <EuiFlexGroup alignItems="center" gutterSize="none"> From cf0a9885b206dc0253b5352e23ce88ca1778f510 Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Mon, 17 Feb 2020 20:31:39 +0200 Subject: [PATCH 10/21] Update jest snapshot --- .../__snapshots__/index.test.tsx.snap | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap index a4bcf20ea1bc2..a100f5e4f93b4 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap @@ -8,21 +8,16 @@ exports[`HeaderPage it renders 1`] = ` alignItems="center" > <FlexItem> - <EuiTitle - size="l" - > - <h1 - data-test-subj="header-page-title" - > - Test title - - <StyledEuiBetaBadge - label="Beta" - tooltipContent="Test tooltip" - tooltipPosition="bottom" - /> - </h1> - </EuiTitle> + <Memo(TitleComponent) + badgeOptions={ + Object { + "beta": true, + "text": "Beta", + "tooltip": "Test tooltip", + } + } + title="Test title" + /> <Subtitle data-test-subj="header-page-subtitle" items="Test subtitle" From 53f56d433ac23c76890224fb14aacd76788aadbc Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Mon, 17 Feb 2020 21:31:04 +0200 Subject: [PATCH 11/21] Rename prop --- .../siem/public/components/header_page/editable_title.tsx | 6 +++--- .../siem/public/pages/case/components/case_view/index.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx index 9db011469d005..74091d0f069d0 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx @@ -15,13 +15,13 @@ interface Props { title: string | React.ReactNode; isEditTitle?: boolean; onChange: (a: string) => void; - onClick: (b: boolean) => void; + onCancel: () => void; onSubmit: () => void; } const EditableTitleComponent: React.FC<Props> = ({ onChange, - onClick, + onCancel, onSubmit, isLoading, title, @@ -39,7 +39,7 @@ const EditableTitleComponent: React.FC<Props> = ({ </EuiButton> </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiButtonEmpty onClick={() => onClick(false)}>{cancelTitle}</EuiButtonEmpty> + <EuiButtonEmpty onClick={onCancel}>{cancelTitle}</EuiButtonEmpty> </EuiFlexItem> </EuiFlexGroup> <EuiFlexItem /> diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 0e64c62eb5c57..82349777d5c50 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -229,7 +229,7 @@ export const Cases = React.memo<CasesProps>(({ caseId, initialData, isLoading }) title={title} onChange={newTitle => setTitle(newTitle)} onSubmit={() => onUpdateField('title', title)} - onClick={isEdit => setIsEditTitle(isEdit)} + onCancel={() => setIsEditTitle(false)} submitTitle={i18n.SUBMIT} cancelTitle={i18n.CANCEL} /> From 41e04444d368960ca7f045ca8e8dc1604b3982a3 Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Mon, 17 Feb 2020 22:10:17 +0200 Subject: [PATCH 12/21] Make EditableTitleComponent more generic --- .../components/header_page/editable_title.tsx | 68 +++++++++++++++---- .../pages/case/components/case_view/index.tsx | 27 +------- 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx index 74091d0f069d0..548cf17f031ae 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx @@ -5,15 +5,35 @@ */ import React from 'react'; +import styled, { css } from 'styled-components'; -import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiFieldText } from '@elastic/eui'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFieldText, + EuiButtonIcon, +} from '@elastic/eui'; + +import { Title } from './title'; + +const StyledEuiButtonIcon = styled(EuiButtonIcon)` + ${({ theme }) => css` + margin-left: ${theme.eui.euiSize}; + `} +`; + +StyledEuiButtonIcon.displayName = 'StyledEuiButtonIcon'; interface Props { submitTitle: string; cancelTitle: string; isLoading: boolean; + onClickEditIcon: () => void; title: string | React.ReactNode; - isEditTitle?: boolean; + editMode: boolean; + editIcon?: string; onChange: (a: string) => void; onCancel: () => void; onSubmit: () => void; @@ -25,25 +45,43 @@ const EditableTitleComponent: React.FC<Props> = ({ onSubmit, isLoading, title, + onClickEditIcon, submitTitle, cancelTitle, -}) => ( - <EuiFlexGroup alignItems="center" gutterSize="m" justifyContent="spaceBetween"> - <EuiFlexItem grow={false}> - <EuiFieldText onChange={e => onChange(e.target.value)} value={`${title}`} /> - </EuiFlexItem> - <EuiFlexGroup gutterSize="none" responsive={false} wrap={true}> + editMode = false, + editIcon = 'pencil', +}) => { + return editMode ? ( + <EuiFlexGroup alignItems="center" gutterSize="m" justifyContent="spaceBetween"> + <EuiFlexItem grow={false}> + <EuiFieldText onChange={e => onChange(e.target.value)} value={`${title}`} /> + </EuiFlexItem> + <EuiFlexGroup gutterSize="none" responsive={false} wrap={true}> + <EuiFlexItem grow={false}> + <EuiButton fill isDisabled={isLoading} isLoading={isLoading} onClick={onSubmit}> + {submitTitle} + </EuiButton> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButtonEmpty onClick={onCancel}>{cancelTitle}</EuiButtonEmpty> + </EuiFlexItem> + </EuiFlexGroup> + <EuiFlexItem /> + </EuiFlexGroup> + ) : ( + <EuiFlexGroup alignItems="center" gutterSize="none"> <EuiFlexItem grow={false}> - <EuiButton fill isDisabled={isLoading} isLoading={isLoading} onClick={onSubmit}> - {submitTitle} - </EuiButton> + <Title title={title} /> </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiButtonEmpty onClick={onCancel}>{cancelTitle}</EuiButtonEmpty> + <StyledEuiButtonIcon + aria-label={title as string} + iconType={editIcon} + onClick={onClickEditIcon} + /> </EuiFlexItem> </EuiFlexGroup> - <EuiFlexItem /> - </EuiFlexGroup> -); + ); +}; export const EditableTitle = React.memo(EditableTitleComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 82349777d5c50..cacc65c64e05a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -16,7 +16,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, - EuiButtonIcon, } from '@elastic/eui'; import styled, { css } from 'styled-components'; @@ -26,7 +25,6 @@ import { Case } from '../../../../containers/case/types'; import { FormattedRelativePreferenceDate } from '../../../../components/formatted_date'; import { getCaseUrl } from '../../../../components/link_to'; import { HeaderPage } from '../../../../components/header_page'; -import { Title } from '../../../../components/header_page/title'; import { EditableTitle } from '../../../../components/header_page/editable_title'; import { Markdown } from '../../../../components/markdown'; import { PropertyActions } from '../property_actions'; @@ -62,14 +60,6 @@ const BackgroundWrapper = styled.div` `} `; -const StyledEuiButtonIcon = styled(EuiButtonIcon)` - ${({ theme }) => css` - margin-left: ${theme.eui.euiSize}; - `} -`; - -StyledEuiButtonIcon.displayName = 'StyledEuiButtonIcon'; - interface CasesProps { caseId: string; initialData: Case; @@ -223,29 +213,18 @@ export const Cases = React.memo<CasesProps>(({ caseId, initialData, isLoading }) }, ]; - const titleNode = isEditTitle ? ( + const titleNode = ( <EditableTitle isLoading={isLoading} title={title} + onClickEditIcon={() => setIsEditTitle(true)} onChange={newTitle => setTitle(newTitle)} onSubmit={() => onUpdateField('title', title)} onCancel={() => setIsEditTitle(false)} submitTitle={i18n.SUBMIT} cancelTitle={i18n.CANCEL} + editMode={isEditTitle} /> - ) : ( - <EuiFlexGroup alignItems="center" gutterSize="none"> - <EuiFlexItem grow={false}> - <Title title={title} /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <StyledEuiButtonIcon - aria-label={title} - iconType="pencil" - onClick={() => setIsEditTitle(true)} - /> - </EuiFlexItem> - </EuiFlexGroup> ); return ( From 254475e6c13c86847f3cfa3ac7f05a1676dd5ce9 Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Tue, 18 Feb 2020 17:15:17 +0200 Subject: [PATCH 13/21] useCallback instead of inline functions in props --- .../pages/case/components/case_view/index.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index cacc65c64e05a..0ce2bdf01a227 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -213,14 +213,19 @@ export const Cases = React.memo<CasesProps>(({ caseId, initialData, isLoading }) }, ]; + const onCancel = useCallback(() => setIsEditTitle(false), []); + const onSubmit = useCallback(() => onUpdateField('title', title), []); + const onTitleChange = useCallback(newTitle => setTitle(newTitle), [title]); + const onClickEditIcon = useCallback(() => setIsEditTitle(true), []); + const titleNode = ( <EditableTitle isLoading={isLoading} title={title} - onClickEditIcon={() => setIsEditTitle(true)} - onChange={newTitle => setTitle(newTitle)} - onSubmit={() => onUpdateField('title', title)} - onCancel={() => setIsEditTitle(false)} + onClickEditIcon={onClickEditIcon} + onChange={onTitleChange} + onSubmit={onSubmit} + onCancel={onCancel} submitTitle={i18n.SUBMIT} cancelTitle={i18n.CANCEL} editMode={isEditTitle} From e65f1f93f9a742cd9a59156d25e7393860c0e778 Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Tue, 18 Feb 2020 18:47:23 +0200 Subject: [PATCH 14/21] Hardcode titles --- .../components/header_page/editable_title.tsx | 5 ++++- .../public/components/header_page/translations.ts | 15 +++++++++++++++ .../case/components/case_view/translations.ts | 8 -------- 3 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/components/header_page/translations.ts diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx index 548cf17f031ae..9f9171e0503bc 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx @@ -16,6 +16,8 @@ import { EuiButtonIcon, } from '@elastic/eui'; +import * as i18n from './translations'; + import { Title } from './title'; const StyledEuiButtonIcon = styled(EuiButtonIcon)` @@ -60,10 +62,11 @@ const EditableTitleComponent: React.FC<Props> = ({ <EuiFlexItem grow={false}> <EuiButton fill isDisabled={isLoading} isLoading={isLoading} onClick={onSubmit}> {submitTitle} + {i18n.SUBMIT} </EuiButton> </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiButtonEmpty onClick={onCancel}>{cancelTitle}</EuiButtonEmpty> + <EuiButtonEmpty onClick={onCancel}>{i18n.CANCEL}</EuiButtonEmpty> </EuiFlexItem> </EuiFlexGroup> <EuiFlexItem /> diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/translations.ts b/x-pack/legacy/plugins/siem/public/components/header_page/translations.ts new file mode 100644 index 0000000000000..23e8f927ac0a0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_page/translations.ts @@ -0,0 +1,15 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const SUBMIT = i18n.translate('xpack.siem.header.editableTitle.submit', { + defaultMessage: 'Submit', +}); + +export const CANCEL = i18n.translate('xpack.siem.header.editableTitle.cancel', { + defaultMessage: 'Cancel', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts index bacd42490937a..f45c52533d2e7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts @@ -43,11 +43,3 @@ export const STATUS = i18n.translate('xpack.siem.case.caseView.statusLabel', { export const CASE_OPENED = i18n.translate('xpack.siem.case.caseView.caseOpened', { defaultMessage: 'Case opened', }); - -export const SUBMIT = i18n.translate('xpack.siem.case.casePage.title.submit', { - defaultMessage: 'Submit', -}); - -export const CANCEL = i18n.translate('xpack.siem.case.casePage.title.cancel', { - defaultMessage: 'Cancel', -}); From 96511224ebf6557796203e753112849463eed2ac Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Tue, 18 Feb 2020 18:48:15 +0200 Subject: [PATCH 15/21] Move UI state inside EditableTitleComponent --- .../components/header_page/editable_title.tsx | 47 +++++++++---------- .../pages/case/components/case_view/index.tsx | 27 ++++------- 2 files changed, 30 insertions(+), 44 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx index 9f9171e0503bc..77055b8840f80 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useState, useCallback } from 'react'; import styled, { css } from 'styled-components'; import { @@ -29,39 +29,36 @@ const StyledEuiButtonIcon = styled(EuiButtonIcon)` StyledEuiButtonIcon.displayName = 'StyledEuiButtonIcon'; interface Props { - submitTitle: string; - cancelTitle: string; isLoading: boolean; - onClickEditIcon: () => void; title: string | React.ReactNode; - editMode: boolean; - editIcon?: string; - onChange: (a: string) => void; - onCancel: () => void; - onSubmit: () => void; + onSubmit: (title: string) => void; } -const EditableTitleComponent: React.FC<Props> = ({ - onChange, - onCancel, - onSubmit, - isLoading, - title, - onClickEditIcon, - submitTitle, - cancelTitle, - editMode = false, - editIcon = 'pencil', -}) => { +const EditableTitleComponent: React.FC<Props> = ({ onSubmit, isLoading, title }) => { + const [editMode, setEditMode] = useState(false); + const [changedTitle, onTitleChange] = useState(title); + + const onCancel = () => setEditMode(false); + const onClickEditIcon = () => setEditMode(true); + + const onClickSubmit = (newTitle: string): void => { + onSubmit(newTitle); + setEditMode(false); + }; + return editMode ? ( <EuiFlexGroup alignItems="center" gutterSize="m" justifyContent="spaceBetween"> <EuiFlexItem grow={false}> - <EuiFieldText onChange={e => onChange(e.target.value)} value={`${title}`} /> + <EuiFieldText onChange={e => onTitleChange(e.target.value)} value={`${changedTitle}`} /> </EuiFlexItem> <EuiFlexGroup gutterSize="none" responsive={false} wrap={true}> <EuiFlexItem grow={false}> - <EuiButton fill isDisabled={isLoading} isLoading={isLoading} onClick={onSubmit}> - {submitTitle} + <EuiButton + fill + isDisabled={isLoading} + isLoading={isLoading} + onClick={() => onClickSubmit(changedTitle as string)} + > {i18n.SUBMIT} </EuiButton> </EuiFlexItem> @@ -79,7 +76,7 @@ const EditableTitleComponent: React.FC<Props> = ({ <EuiFlexItem grow={false}> <StyledEuiButtonIcon aria-label={title as string} - iconType={editIcon} + iconType="pencil" onClick={onClickEditIcon} /> </EuiFlexItem> diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 0ce2bdf01a227..a92cf99097fce 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -69,7 +69,6 @@ interface CasesProps { export const Cases = React.memo<CasesProps>(({ caseId, initialData, isLoading }) => { const [{ data }, dispatchUpdateCaseProperty] = useUpdateCase(caseId, initialData); const [isEditDescription, setIsEditDescription] = useState(false); - const [isEditTitle, setIsEditTitle] = useState(false); const [isEditTags, setIsEditTags] = useState(false); const [isCaseOpen, setIsCaseOpen] = useState(data.state === 'open'); const [description, setDescription] = useState(data.description); @@ -85,7 +84,6 @@ export const Cases = React.memo<CasesProps>(({ caseId, initialData, isLoading }) updateKey: 'title', updateValue, }); - setIsEditTitle(false); } break; case 'description': @@ -213,25 +211,16 @@ export const Cases = React.memo<CasesProps>(({ caseId, initialData, isLoading }) }, ]; - const onCancel = useCallback(() => setIsEditTitle(false), []); - const onSubmit = useCallback(() => onUpdateField('title', title), []); - const onTitleChange = useCallback(newTitle => setTitle(newTitle), [title]); - const onClickEditIcon = useCallback(() => setIsEditTitle(true), []); - - const titleNode = ( - <EditableTitle - isLoading={isLoading} - title={title} - onClickEditIcon={onClickEditIcon} - onChange={onTitleChange} - onSubmit={onSubmit} - onCancel={onCancel} - submitTitle={i18n.SUBMIT} - cancelTitle={i18n.CANCEL} - editMode={isEditTitle} - /> + const onSubmit = useCallback( + newTitle => { + onUpdateField('title', newTitle); + setTitle(newTitle); + }, + [title] ); + const titleNode = <EditableTitle isLoading={isLoading} title={title} onSubmit={onSubmit} />; + return ( <> <MyWrapper> From 0ef005d9c6106d0a80cd33c62b19f9aa324903c2 Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Tue, 18 Feb 2020 19:04:06 +0200 Subject: [PATCH 16/21] Seperate title's tests --- .../__snapshots__/title.test.tsx.snap | 19 +++++ .../components/header_page/index.test.tsx | 45 ------------ .../components/header_page/title.test.tsx | 72 +++++++++++++++++++ 3 files changed, 91 insertions(+), 45 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/title.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/header_page/title.test.tsx diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/title.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/title.test.tsx.snap new file mode 100644 index 0000000000000..05af2fee2c2a2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/title.test.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Title it renders 1`] = ` +<EuiTitle + size="l" +> + <h1 + data-test-subj="header-page-title" + > + Test title + + <StyledEuiBetaBadge + label="Beta" + tooltipContent="Test tooltip" + tooltipPosition="bottom" + /> + </h1> +</EuiTitle> +`; diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx index 83a70fd90d82b..05575f060bb36 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx @@ -31,21 +31,6 @@ describe('HeaderPage', () => { expect(wrapper).toMatchSnapshot(); }); - test('it renders the title', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage title="Test title" /> - </TestProviders> - ); - - expect( - wrapper - .find('[data-test-subj="header-page-title"]') - .first() - .exists() - ).toBe(true); - }); - test('it renders the back link when provided', () => { const wrapper = mount( <TestProviders> @@ -191,34 +176,4 @@ describe('HeaderPage', () => { expect(siemHeaderPage).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); expect(siemHeaderPage).not.toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l); }); - - test('it renders as a draggable when arguments provided', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage draggableArguments={{ field: 'neat', value: 'cool' }} title="Test title" /> - </TestProviders> - ); - - expect( - wrapper - .find('[data-test-subj="header-page-draggable"]') - .first() - .exists() - ).toBe(true); - }); - - test('it DOES NOT render as a draggable when arguments not provided', () => { - const wrapper = mount( - <TestProviders> - <HeaderPage title="Test title" /> - </TestProviders> - ); - - expect( - wrapper - .find('[data-test-subj="header-page-draggable"]') - .first() - .exists() - ).toBe(false); - }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/title.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/title.test.tsx new file mode 100644 index 0000000000000..48cb05914d4c5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_page/title.test.tsx @@ -0,0 +1,72 @@ +/* + * 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 { shallow } from 'enzyme'; +import React from 'react'; + +import { TestProviders } from '../../mock'; +import { Title } from './title'; +import { useMountAppended } from '../../utils/use_mount_appended'; + +describe('Title', () => { + const mount = useMountAppended(); + + test('it renders', () => { + const wrapper = shallow( + <Title + badgeOptions={{ beta: true, text: 'Beta', tooltip: 'Test tooltip' }} + title="Test title" + /> + ); + + expect(wrapper).toMatchSnapshot(); + }); + + test('it renders the title', () => { + const wrapper = mount( + <TestProviders> + <Title title="Test title" /> + </TestProviders> + ); + + expect( + wrapper + .find('[data-test-subj="header-page-title"]') + .first() + .exists() + ).toBe(true); + }); + + test('it renders as a draggable when arguments provided', () => { + const wrapper = mount( + <TestProviders> + <Title draggableArguments={{ field: 'neat', value: 'cool' }} title="Test title" /> + </TestProviders> + ); + + expect( + wrapper + .find('[data-test-subj="header-page-draggable"]') + .first() + .exists() + ).toBe(true); + }); + + test('it DOES NOT render as a draggable when arguments not provided', () => { + const wrapper = mount( + <TestProviders> + <Title title="Test title" /> + </TestProviders> + ); + + expect( + wrapper + .find('[data-test-subj="header-page-draggable"]') + .first() + .exists() + ).toBe(false); + }); +}); From 9d0a696b06c51ff02c15c1b4c1b74cfa5349ceff Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Tue, 18 Feb 2020 20:41:49 +0200 Subject: [PATCH 17/21] Create tests for EditableTitleComponent --- .../editable_title.test.tsx.snap | 26 +++ .../header_page/editable_title.test.tsx | 162 ++++++++++++++++++ .../components/header_page/editable_title.tsx | 12 +- 3 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/editable_title.test.tsx.snap create mode 100644 x-pack/legacy/plugins/siem/public/components/header_page/editable_title.test.tsx diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/editable_title.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/editable_title.test.tsx.snap new file mode 100644 index 0000000000000..b1e412a9ace1b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/editable_title.test.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EditableTitle it renders 1`] = ` +<EuiFlexGroup + alignItems="center" + gutterSize="none" +> + <EuiFlexItem + grow={false} + > + <Memo(TitleComponent) + title="Test title" + /> + </EuiFlexItem> + <EuiFlexItem + grow={false} + > + <StyledEuiButtonIcon + aria-label="Test title" + data-test-subj="editable-title-edit-icon" + iconType="pencil" + onClick={[Function]} + /> + </EuiFlexItem> +</EuiFlexGroup> +`; diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.test.tsx new file mode 100644 index 0000000000000..94f92af622513 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.test.tsx @@ -0,0 +1,162 @@ +/* + * 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 { shallow } from 'enzyme'; +import React from 'react'; + +import { TestProviders } from '../../mock'; +import { EditableTitle } from './editable_title'; +import { useMountAppended } from '../../utils/use_mount_appended'; + +describe('EditableTitle', () => { + const mount = useMountAppended(); + const submitTitle = jest.fn(); + + test('it renders', () => { + const wrapper = shallow( + <EditableTitle title="Test title" onSubmit={submitTitle} isLoading={false} /> + ); + + expect(wrapper).toMatchSnapshot(); + }); + + test('it shows the edit title input field', () => { + const wrapper = mount( + <TestProviders> + <EditableTitle title="Test title" onSubmit={submitTitle} isLoading={false} /> + </TestProviders> + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + expect( + wrapper + .find('[data-test-subj="editable-title-input-field"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the submit button', () => { + const wrapper = mount( + <TestProviders> + <EditableTitle title="Test title" onSubmit={submitTitle} isLoading={false} /> + </TestProviders> + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + expect( + wrapper + .find('[data-test-subj="editable-title-submit-btn"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the cancel button', () => { + const wrapper = mount( + <TestProviders> + <EditableTitle title="Test title" onSubmit={submitTitle} isLoading={false} /> + </TestProviders> + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + expect( + wrapper + .find('[data-test-subj="editable-title-cancel-btn"]') + .first() + .exists() + ).toBe(true); + }); + + test('it DOES NOT shows the edit icon when in edit mode', () => { + const wrapper = mount( + <TestProviders> + <EditableTitle title="Test title" onSubmit={submitTitle} isLoading={false} /> + </TestProviders> + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + expect( + wrapper + .find('[data-test-subj="editable-title-edit-icon"]') + .first() + .exists() + ).toBe(false); + }); + + test('it switch to non edit mode when canceled', () => { + const wrapper = mount( + <TestProviders> + <EditableTitle title="Test title" onSubmit={submitTitle} isLoading={false} /> + </TestProviders> + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="editable-title-cancel-btn"]').simulate('click'); + + expect( + wrapper + .find('[data-test-subj="editable-title-edit-icon"]') + .first() + .exists() + ).toBe(true); + }); + + test('it should change the title', () => { + const newTitle = 'new test title'; + + const wrapper = mount( + <TestProviders> + <EditableTitle title="Test title" onSubmit={submitTitle} isLoading={false} /> + </TestProviders> + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + wrapper + .find('input[data-test-subj="editable-title-input-field"]') + .simulate('change', { target: { value: newTitle } }); + + wrapper.update(); + + expect( + wrapper.find('input[data-test-subj="editable-title-input-field"]').prop('value') + ).toEqual(newTitle); + }); + + test('it submits the title', () => { + const newTitle = 'new test title'; + + const wrapper = mount( + <TestProviders> + <EditableTitle title="Test title" onSubmit={submitTitle} isLoading={false} /> + </TestProviders> + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + wrapper + .find('input[data-test-subj="editable-title-input-field"]') + .simulate('change', { target: { value: newTitle } }); + + wrapper.find('button[data-test-subj="editable-title-submit-btn"]').simulate('click'); + wrapper.update(); + + expect(submitTitle).toHaveBeenCalled(); + expect(submitTitle.mock.calls[0][0]).toEqual(newTitle); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx index 77055b8840f80..d3a6cacf14a29 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx @@ -49,7 +49,11 @@ const EditableTitleComponent: React.FC<Props> = ({ onSubmit, isLoading, title }) return editMode ? ( <EuiFlexGroup alignItems="center" gutterSize="m" justifyContent="spaceBetween"> <EuiFlexItem grow={false}> - <EuiFieldText onChange={e => onTitleChange(e.target.value)} value={`${changedTitle}`} /> + <EuiFieldText + onChange={e => onTitleChange(e.target.value)} + value={`${changedTitle}`} + data-test-subj="editable-title-input-field" + /> </EuiFlexItem> <EuiFlexGroup gutterSize="none" responsive={false} wrap={true}> <EuiFlexItem grow={false}> @@ -58,12 +62,15 @@ const EditableTitleComponent: React.FC<Props> = ({ onSubmit, isLoading, title }) isDisabled={isLoading} isLoading={isLoading} onClick={() => onClickSubmit(changedTitle as string)} + data-test-subj="editable-title-submit-btn" > {i18n.SUBMIT} </EuiButton> </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiButtonEmpty onClick={onCancel}>{i18n.CANCEL}</EuiButtonEmpty> + <EuiButtonEmpty onClick={onCancel} data-test-subj="editable-title-cancel-btn"> + {i18n.CANCEL} + </EuiButtonEmpty> </EuiFlexItem> </EuiFlexGroup> <EuiFlexItem /> @@ -78,6 +85,7 @@ const EditableTitleComponent: React.FC<Props> = ({ onSubmit, isLoading, title }) aria-label={title as string} iconType="pencil" onClick={onClickEditIcon} + data-test-subj="editable-title-edit-icon" /> </EuiFlexItem> </EuiFlexGroup> From c3aba930c9cf1c33f1b7e320a539409f9aec4c58 Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Tue, 18 Feb 2020 21:09:09 +0200 Subject: [PATCH 18/21] useCallbacks on EditableTitle component --- .../components/header_page/editable_title.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx index d3a6cacf14a29..850ddcd3c98fd 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx @@ -38,13 +38,16 @@ const EditableTitleComponent: React.FC<Props> = ({ onSubmit, isLoading, title }) const [editMode, setEditMode] = useState(false); const [changedTitle, onTitleChange] = useState(title); - const onCancel = () => setEditMode(false); - const onClickEditIcon = () => setEditMode(true); + const onCancel = useCallback(() => setEditMode(false), []); + const onClickEditIcon = useCallback(() => setEditMode(true), []); - const onClickSubmit = (newTitle: string): void => { - onSubmit(newTitle); - setEditMode(false); - }; + const onClickSubmit = useCallback( + (newTitle: string): void => { + onSubmit(newTitle); + setEditMode(false); + }, + [changedTitle] + ); return editMode ? ( <EuiFlexGroup alignItems="center" gutterSize="m" justifyContent="spaceBetween"> From 4ed8b6980861cd2272f6b4b629f9f044153150f9 Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Wed, 19 Feb 2020 13:46:05 +0200 Subject: [PATCH 19/21] Create translation for aria-label in edit icon --- .../siem/public/components/header_page/editable_title.tsx | 2 +- .../siem/public/components/header_page/translations.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx index 850ddcd3c98fd..bd8dabfc35ff4 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx @@ -85,7 +85,7 @@ const EditableTitleComponent: React.FC<Props> = ({ onSubmit, isLoading, title }) </EuiFlexItem> <EuiFlexItem grow={false}> <StyledEuiButtonIcon - aria-label={title as string} + aria-label={i18n.EDIT_TITLE_ARIA(title as string)} iconType="pencil" onClick={onClickEditIcon} data-test-subj="editable-title-edit-icon" diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/translations.ts b/x-pack/legacy/plugins/siem/public/components/header_page/translations.ts index 23e8f927ac0a0..2bc2ac492b0b1 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/header_page/translations.ts @@ -13,3 +13,9 @@ export const SUBMIT = i18n.translate('xpack.siem.header.editableTitle.submit', { export const CANCEL = i18n.translate('xpack.siem.header.editableTitle.cancel', { defaultMessage: 'Cancel', }); + +export const EDIT_TITLE_ARIA = (title: string) => + i18n.translate('xpack.siem.header.editableTitle.editButtonAria', { + values: { title }, + defaultMessage: 'You can edit {title} by clicking', + }); From 9bd3547848901928b983757dfe13d09e55dd4c99 Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Wed, 19 Feb 2020 14:04:49 +0200 Subject: [PATCH 20/21] Check if switched to edit mode after pressing submit --- .../header_page/__snapshots__/editable_title.test.tsx.snap | 2 +- .../public/components/header_page/editable_title.test.tsx | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/editable_title.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/editable_title.test.tsx.snap index b1e412a9ace1b..30c70d7f5a2a6 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/editable_title.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/editable_title.test.tsx.snap @@ -16,7 +16,7 @@ exports[`EditableTitle it renders 1`] = ` grow={false} > <StyledEuiButtonIcon - aria-label="Test title" + aria-label="You can edit Test title by clicking" data-test-subj="editable-title-edit-icon" iconType="pencil" onClick={[Function]} diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.test.tsx index 94f92af622513..2946c22efa0cb 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.test.tsx @@ -158,5 +158,11 @@ describe('EditableTitle', () => { expect(submitTitle).toHaveBeenCalled(); expect(submitTitle.mock.calls[0][0]).toEqual(newTitle); + expect( + wrapper + .find('[data-test-subj="editable-title-edit-icon"]') + .first() + .exists() + ).toBe(true); }); }); From 4d940bd67c5364cf1cd19264c13daa68145d2fce Mon Sep 17 00:00:00 2001 From: Christos Nasikas <christos.nasikas@elastic.co> Date: Wed, 19 Feb 2020 15:57:16 +0200 Subject: [PATCH 21/21] Test title when canceled --- .../header_page/editable_title.test.tsx | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.test.tsx index 2946c22efa0cb..fcfdd0d5dd237 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.test.tsx @@ -137,6 +137,30 @@ describe('EditableTitle', () => { ).toEqual(newTitle); }); + test('it should NOT change the title when cancel', () => { + const title = 'Test title'; + const newTitle = 'new test title'; + + const wrapper = mount( + <TestProviders> + <EditableTitle title={title} onSubmit={submitTitle} isLoading={false} /> + </TestProviders> + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + wrapper + .find('input[data-test-subj="editable-title-input-field"]') + .simulate('change', { target: { value: newTitle } }); + wrapper.update(); + + wrapper.find('button[data-test-subj="editable-title-cancel-btn"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find('h1[data-test-subj="header-page-title"]').text()).toEqual(title); + }); + test('it submits the title', () => { const newTitle = 'new test title';