diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_actions.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_actions.tsx index 0b60555b28f15..6b270f01da2ff 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_actions.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_actions.tsx @@ -7,10 +7,10 @@ import type { VFC } from 'react'; import React, { memo } from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiButtonIcon, EuiCopy, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { copyFunction } from '../../../shared/utils/copy_to_clipboard'; import { FLYOUT_URL_PARAM } from '../../shared/hooks/url/use_sync_flyout_state_with_url'; -import { CopyToClipboard } from '../../../shared/components/copy_to_clipboard'; import { useGetAlertDetailsFlyoutLink } from '../../../../timelines/components/side_panel/event_details/use_get_alert_details_flyout_link'; import { useBasicDataFromDetailsData } from '../../../../timelines/components/side_panel/event_details/helpers'; import { useRightPanelContext } from '../context'; @@ -31,26 +31,32 @@ export const HeaderActions: VFC = memo(() => { const showShareAlertButton = isAlert && alertDetailsLink; + const modifier = (value: string) => { + const query = new URLSearchParams(window.location.search); + return `${value}&${FLYOUT_URL_PARAM}=${query.get(FLYOUT_URL_PARAM)}`; + }; + return ( {showShareAlertButton && ( - { - const query = new URLSearchParams(window.location.search); - return `${value}&${FLYOUT_URL_PARAM}=${query.get(FLYOUT_URL_PARAM)}`; - }} - iconType={'share'} - color={'text'} - ariaLabel={i18n.translate( - 'xpack.securitySolution.flyout.right.header.shareButtonAriaLabel', - { - defaultMessage: 'Share Alert', - } + + {(copy) => ( + copyFunction(copy, alertDetailsLink, modifier)} + onKeyDown={() => copyFunction(copy, alertDetailsLink, modifier)} + /> )} - data-test-subj={SHARE_BUTTON_TEST_ID} - /> + )} diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/json_tab.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/json_tab.tsx index f29a1486819cb..6d329811f7228 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/json_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/json_tab.tsx @@ -8,10 +8,10 @@ import type { FC } from 'react'; import React, { memo, useEffect, useRef, useState } from 'react'; import { JsonCodeEditor } from '@kbn/unified-doc-viewer-plugin/public'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiButtonEmpty, EuiCopy, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { CopyToClipboard } from '../../../shared/components/copy_to_clipboard'; +import { copyFunction } from '../../../shared/utils/copy_to_clipboard'; import { JSON_TAB_CONTENT_TEST_ID, JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID } from './test_ids'; import { useRightPanelContext } from '../context'; @@ -48,31 +48,35 @@ export const JsonTab: FC = memo(() => { return ( - - } - iconType={'copyClipboard'} - size={'xs'} - ariaLabel={i18n.translate( - 'xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonAriaLabel', - { - defaultMessage: 'Copy to clipboard', - } + + {(copy) => ( + copyFunction(copy, jsonValue)} + onKeyDown={() => copyFunction(copy, jsonValue)} + > + + )} - data-test-subj={JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID} - /> + diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/copy_to_clipboard.stories.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/copy_to_clipboard.stories.tsx deleted file mode 100644 index 16519305d2a00..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/shared/components/copy_to_clipboard.stories.tsx +++ /dev/null @@ -1,125 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import type { Story } from '@storybook/react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { CopyToClipboard } from './copy_to_clipboard'; - -export default { - component: CopyToClipboard, - title: 'Flyout/CopyToClipboard', -}; - -const json = JSON.stringify({ - foo: 'bar', -}); - -export const Default: Story = () => { - return ( - {'Copy'}

} - iconType={'copyClipboard'} - ariaLabel={'Copy'} - /> - ); -}; - -export const WithModifier: Story = () => { - return ( - { - window.alert('modifier'); - return value; - }} - text={

{'Copy'}

} - iconType={'copyClipboard'} - ariaLabel={'Copy'} - /> - ); -}; - -export const MultipleSizes: Story = () => { - return ( - - - {'xs size'}

} - iconType={'copyClipboard'} - size={'xs'} - ariaLabel={'Copy'} - /> -
- - {'s size'}

} - iconType={'copyClipboard'} - size={'s'} - ariaLabel={'Copy'} - /> -
- - {'m size'}

} - iconType={'copyClipboard'} - size={'m'} - ariaLabel={'Copy'} - /> -
-
- ); -}; - -export const ButtonOnly: Story = () => { - return ( - { - window.alert('modifier'); - return value; - }} - iconType={'copyClipboard'} - ariaLabel={'Copy'} - /> - ); -}; - -export const CustomColor: Story = () => { - return ( - { - window.alert('modifier'); - return value; - }} - iconType={'copyClipboard'} - ariaLabel={'Copy'} - text={

{'showing custom color'}

} - color={'accent'} - /> - ); -}; - -export const CustomIcon: Story = () => { - return ( - { - window.alert('modifier'); - return value; - }} - iconType={'share'} - ariaLabel={'Share'} - text={

{'custom icon'}

} - /> - ); -}; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/copy_to_clipboard.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/copy_to_clipboard.test.tsx deleted file mode 100644 index 1f9c5976f18a9..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/shared/components/copy_to_clipboard.test.tsx +++ /dev/null @@ -1,72 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; -import { render } from '@testing-library/react'; -import React from 'react'; -import type { CopyToClipboardProps } from './copy_to_clipboard'; -import { CopyToClipboard } from './copy_to_clipboard'; - -jest.mock('@elastic/eui', () => ({ - ...jest.requireActual('@elastic/eui'), - copyToClipboard: jest.fn(), - EuiCopy: jest.fn(({ children: functionAsChild }) => functionAsChild(jest.fn())), -})); - -const renderShareButton = (props: CopyToClipboardProps) => - render( - - - - ); - -describe('ShareButton', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should render the copy to clipboard button', () => { - const text = 'text'; - - const props = { - rawValue: 'rawValue', - text: {text}, - iconType: 'iconType', - ariaLabel: 'ariaLabel', - 'data-test-subj': 'data-test-subj', - }; - const { getByTestId, getByText } = renderShareButton(props); - - const button = getByTestId('data-test-subj'); - - expect(button).toBeInTheDocument(); - expect(button).toHaveAttribute('aria-label', props.ariaLabel); - expect(button).toHaveAttribute('type', 'button'); - - expect(getByText(text)).toBeInTheDocument(); - }); - - it('should use modifier if provided', () => { - const modifiedFc = jest.fn(); - - const props = { - rawValue: 'rawValue', - modifier: modifiedFc, - text: {'text'}, - iconType: 'iconType', - ariaLabel: 'ariaLabel', - 'data-test-subj': 'data-test-subj', - }; - const { getByTestId } = renderShareButton(props); - - const button = getByTestId('data-test-subj'); - - button.click(); - - expect(modifiedFc).toHaveBeenCalledWith(props.rawValue); - }); -}); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/copy_to_clipboard.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/copy_to_clipboard.tsx deleted file mode 100644 index 0727349543d8e..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/shared/components/copy_to_clipboard.tsx +++ /dev/null @@ -1,88 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { EuiButtonEmptyProps } from '@elastic/eui'; -import { copyToClipboard, EuiButtonEmpty, EuiCopy } from '@elastic/eui'; -import type { FC, ReactElement } from 'react'; -import React from 'react'; - -export interface CopyToClipboardProps { - /** - * Value to save to the clipboard - */ - rawValue: string; - /** - * Function to modify the raw value before saving to the clipboard - */ - modifier?: (rawValue: string) => string; - /** - * Button main text (next to icon) - */ - text?: ReactElement; - /** - * Icon name (value coming from EUI) - */ - iconType: EuiButtonEmptyProps['iconType']; - /** - * Button size (values coming from EUI) - */ - size?: EuiButtonEmptyProps['size']; - /** - * Optional button color - */ - color?: EuiButtonEmptyProps['color']; - /** - * Aria label value for the button - */ - ariaLabel: string; - /** - Data test subject string for testing - */ - ['data-test-subj']?: string; -} - -/** - * Copy to clipboard component - */ -export const CopyToClipboard: FC = ({ - rawValue, - modifier, - text, - iconType, - size = 'm', - color = 'primary', - ariaLabel, - 'data-test-subj': dataTestSubj, -}) => { - return ( - - {(copy) => ( - { - copy(); - - if (modifier) { - const modifiedCopyValue = modifier(rawValue); - copyToClipboard(modifiedCopyValue); - } else { - copyToClipboard(rawValue); - } - }} - iconType={iconType} - size={size} - color={color} - aria-label={ariaLabel} - data-test-subj={dataTestSubj} - > - {text} - - )} - - ); -}; - -CopyToClipboard.displayName = 'CopyToClipboard'; diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_navigation.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_navigation.tsx index 3629a711b92d0..47c88da58f693 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_navigation.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_navigation.tsx @@ -105,7 +105,7 @@ export const FlyoutNavigation: FC = memo( responsive={false} css={css` padding-left: ${euiTheme.size.s}; - padding-right: ${euiTheme.size.l}; + padding-right: ${euiTheme.size.xl}; height: ${euiTheme.size.xxl}; `} > diff --git a/x-pack/plugins/security_solution/public/flyout/shared/utils/copy_to_clipboard.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/utils/copy_to_clipboard.test.tsx new file mode 100644 index 0000000000000..07816046aa4e1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/utils/copy_to_clipboard.test.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { copyFunction } from './copy_to_clipboard'; + +jest.mock('@elastic/eui', () => ({ + ...jest.requireActual('@elastic/eui'), + copyToClipboard: jest.fn(), + EuiCopy: jest.fn(({ children: functionAsChild }) => functionAsChild(jest.fn())), +})); + +describe('copyFunction', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const rawValue = 'rawValue'; + + it('should call copy function', () => { + const euiCopy = jest.fn(); + + copyFunction(euiCopy, rawValue); + + expect(euiCopy).toHaveBeenCalled(); + }); + + it('should call modifier function if passed', () => { + const euiCopy = jest.fn(); + const modifiedFc = jest.fn(); + + copyFunction(euiCopy, rawValue, modifiedFc); + + expect(modifiedFc).toHaveBeenCalledWith(rawValue); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/shared/utils/copy_to_clipboard.tsx b/x-pack/plugins/security_solution/public/flyout/shared/utils/copy_to_clipboard.tsx new file mode 100644 index 0000000000000..efc3523edac34 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/shared/utils/copy_to_clipboard.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { copyToClipboard } from '@elastic/eui'; + +/** + * Copy to clipboard wrapper component. It allows adding a copy to clipboard functionality to any element. + * It expects the value to be copied with an optional function to modify the value if necessary. + * + * @param copy the copy method from EuiCopy + * @param rawValue the value to save to the clipboard + * @param modifier a function to modify the raw value before saving to the clipboard + */ +export const copyFunction = ( + copy: Function, + rawValue: string, + modifier?: (rawValue: string) => string +) => { + copy(); + + if (modifier) { + const modifiedCopyValue = modifier(rawValue); + copyToClipboard(modifiedCopyValue); + } else { + copyToClipboard(rawValue); + } +};