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..30c70d7f5a2a6 --- /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`] = ` + + + + + + + + +`; 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" > - -

- Test title - - -

-
+ +

+ Test title + + +

+ +`; 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..fcfdd0d5dd237 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.test.tsx @@ -0,0 +1,192 @@ +/* + * 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( + + ); + + expect(wrapper).toMatchSnapshot(); + }); + + test('it shows the edit title input field', () => { + const wrapper = mount( + + + + ); + + 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( + + + + ); + + 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( + + + + ); + + 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( + + + + ); + + 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( + + + + ); + + 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( + + + + ); + + 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 should NOT change the title when cancel', () => { + const title = 'Test title'; + const newTitle = 'new test title'; + + const wrapper = mount( + + + + ); + + 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'; + + const wrapper = mount( + + + + ); + + 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); + expect( + wrapper + .find('[data-test-subj="editable-title-edit-icon"]') + .first() + .exists() + ).toBe(true); + }); +}); 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..bd8dabfc35ff4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_page/editable_title.tsx @@ -0,0 +1,98 @@ +/* + * 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, { useState, useCallback } from 'react'; +import styled, { css } from 'styled-components'; + +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFieldText, + EuiButtonIcon, +} from '@elastic/eui'; + +import * as i18n from './translations'; + +import { Title } from './title'; + +const StyledEuiButtonIcon = styled(EuiButtonIcon)` + ${({ theme }) => css` + margin-left: ${theme.eui.euiSize}; + `} +`; + +StyledEuiButtonIcon.displayName = 'StyledEuiButtonIcon'; + +interface Props { + isLoading: boolean; + title: string | React.ReactNode; + onSubmit: (title: string) => void; +} + +const EditableTitleComponent: React.FC = ({ onSubmit, isLoading, title }) => { + const [editMode, setEditMode] = useState(false); + const [changedTitle, onTitleChange] = useState(title); + + const onCancel = useCallback(() => setEditMode(false), []); + const onClickEditIcon = useCallback(() => setEditMode(true), []); + + const onClickSubmit = useCallback( + (newTitle: string): void => { + onSubmit(newTitle); + setEditMode(false); + }, + [changedTitle] + ); + + return editMode ? ( + + + onTitleChange(e.target.value)} + value={`${changedTitle}`} + data-test-subj="editable-title-input-field" + /> + + + + onClickSubmit(changedTitle as string)} + data-test-subj="editable-title-submit-btn" + > + {i18n.SUBMIT} + + + + + {i18n.CANCEL} + + + + + + ) : ( + + + + </EuiFlexItem> + <EuiFlexItem grow={false}> + <StyledEuiButtonIcon + aria-label={i18n.EDIT_TITLE_ARIA(title as string)} + iconType="pencil" + onClick={onClickEditIcon} + data-test-subj="editable-title-edit-icon" + /> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; + +export const EditableTitle = React.memo(EditableTitleComponent); 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/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx index d694aad3dd896..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,20 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiBetaBadge, - EuiBadge, - EuiFlexGroup, - EuiFlexItem, - EuiProgress, - EuiTitle, -} 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; @@ -63,28 +57,11 @@ const Badge = styled(EuiBadge)` `; Badge.displayName = 'Badge'; -const StyledEuiBetaBadge = styled(EuiBetaBadge)` - vertical-align: middle; -`; - -StyledEuiBetaBadge.displayName = 'StyledEuiBetaBadge'; - interface BackOptions { href: LinkIconProps['href']; text: LinkIconProps['children']; } -interface BadgeOptions { - beta?: boolean; - text: string; - tooltip?: string; -} - -interface DraggableArguments { - field: string; - value: string; -} - export interface HeaderPageProps extends HeaderProps { backOptions?: BackOptions; badgeOptions?: BadgeOptions; @@ -92,7 +69,8 @@ export interface HeaderPageProps extends HeaderProps { draggableArguments?: DraggableArguments; subtitle?: SubtitleProps['items']; subtitle2?: SubtitleProps['items']; - title: string | React.ReactNode; + title: TitleProp; + titleNode?: React.ReactElement; } const HeaderPageComponent: React.FC<HeaderPageProps> = ({ @@ -105,6 +83,7 @@ const HeaderPageComponent: React.FC<HeaderPageProps> = ({ subtitle, subtitle2, title, + titleNode, ...rest }) => ( <Header border={border} {...rest}> @@ -118,34 +97,13 @@ const HeaderPageComponent: React.FC<HeaderPageProps> = ({ </LinkBack> )} - <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> - )} - </> - )} - </h1> - </EuiTitle> + {titleNode || ( + <Title + draggableArguments={draggableArguments} + title={title} + badgeOptions={badgeOptions} + /> + )} {subtitle && <Subtitle data-test-subj="header-page-subtitle" items={subtitle} />} {subtitle2 && <Subtitle data-test-subj="header-page-subtitle-2" items={subtitle2} />} 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); + }); +}); 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<Props> = ({ draggableArguments, title, badgeOptions }) => ( + <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> + )} + </> + )} + </h1> + </EuiTitle> +); + +export const Title = React.memo(TitleComponent); 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..2bc2ac492b0b1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/header_page/translations.ts @@ -0,0 +1,21 @@ +/* + * 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', +}); + +export const EDIT_TITLE_ARIA = (title: string) => + i18n.translate('xpack.siem.header.editableTitle.editButtonAria', { + values: { title }, + defaultMessage: 'You can edit {title} by clicking', + }); 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/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', -}); 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..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 @@ -24,7 +24,8 @@ 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 { EditableTitle } from '../../../../components/header_page/editable_title'; import { Markdown } from '../../../../components/markdown'; import { PropertyActions } from '../property_actions'; import { TagList } from '../tag_list'; @@ -50,6 +51,7 @@ const MyDescriptionList = styled(EuiDescriptionList)` const MyWrapper = styled(WrapperPage)` padding-bottom: 0; `; + const BackgroundWrapper = styled.div` ${({ theme }) => css` background-color: ${theme.eui.euiColorEmptyShade}; @@ -67,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); @@ -83,7 +84,6 @@ export const Cases = React.memo<CasesProps>(({ caseId, initialData, isLoading }) updateKey: 'title', updateValue, }); - setIsEditTitle(false); } break; case 'description': @@ -210,6 +210,17 @@ export const Cases = React.memo<CasesProps>(({ caseId, initialData, isLoading }) ), }, ]; + + const onSubmit = useCallback( + newTitle => { + onUpdateField('title', newTitle); + setTitle(newTitle); + }, + [title] + ); + + const titleNode = <EditableTitle isLoading={isLoading} title={title} onSubmit={onSubmit} />; + return ( <> <MyWrapper> @@ -218,14 +229,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} + titleNode={titleNode} title={title} > <EuiFlexGroup gutterSize="l" justifyContent="flexEnd">