mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] make copytoclipboard component more generic (#170700)
This commit is contained in:
parent
480fcef698
commit
d1f5a070c8
8 changed files with 119 additions and 324 deletions
|
@ -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 (
|
||||
<EuiFlexGroup direction="row" justifyContent="flexEnd">
|
||||
{showShareAlertButton && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<CopyToClipboard
|
||||
rawValue={alertDetailsLink}
|
||||
modifier={(value: string) => {
|
||||
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',
|
||||
}
|
||||
<EuiCopy textToCopy={alertDetailsLink}>
|
||||
{(copy) => (
|
||||
<EuiButtonIcon
|
||||
iconType={'share'}
|
||||
color={'text'}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.securitySolution.flyout.right.header.shareButtonAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Share Alert',
|
||||
}
|
||||
)}
|
||||
data-test-subj={SHARE_BUTTON_TEST_ID}
|
||||
onClick={() => copyFunction(copy, alertDetailsLink, modifier)}
|
||||
onKeyDown={() => copyFunction(copy, alertDetailsLink, modifier)}
|
||||
/>
|
||||
)}
|
||||
data-test-subj={SHARE_BUTTON_TEST_ID}
|
||||
/>
|
||||
</EuiCopy>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -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 (
|
||||
<EuiFlexGroup
|
||||
ref={flexGroupElement}
|
||||
direction={'column'}
|
||||
gutterSize={'none'}
|
||||
direction="column"
|
||||
gutterSize="none"
|
||||
data-test-subj={JSON_TAB_CONTENT_TEST_ID}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent={'flexEnd'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<CopyToClipboard
|
||||
rawValue={jsonValue}
|
||||
text={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonLabel"
|
||||
defaultMessage="Copy to clipboard"
|
||||
/>
|
||||
}
|
||||
iconType={'copyClipboard'}
|
||||
size={'xs'}
|
||||
ariaLabel={i18n.translate(
|
||||
'xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Copy to clipboard',
|
||||
}
|
||||
<EuiCopy textToCopy={jsonValue}>
|
||||
{(copy) => (
|
||||
<EuiButtonEmpty
|
||||
iconType={'copyClipboard'}
|
||||
size={'xs'}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Copy to clipboard',
|
||||
}
|
||||
)}
|
||||
data-test-subj={JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID}
|
||||
onClick={() => copyFunction(copy, jsonValue)}
|
||||
onKeyDown={() => copyFunction(copy, jsonValue)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.right.jsonTab.copyToClipboardButtonLabel"
|
||||
defaultMessage="Copy to clipboard"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
data-test-subj={JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID}
|
||||
/>
|
||||
</EuiCopy>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -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<void> = () => {
|
||||
return (
|
||||
<CopyToClipboard
|
||||
rawValue={json}
|
||||
text={<p>{'Copy'}</p>}
|
||||
iconType={'copyClipboard'}
|
||||
ariaLabel={'Copy'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithModifier: Story<void> = () => {
|
||||
return (
|
||||
<CopyToClipboard
|
||||
rawValue={json}
|
||||
modifier={(value) => {
|
||||
window.alert('modifier');
|
||||
return value;
|
||||
}}
|
||||
text={<p>{'Copy'}</p>}
|
||||
iconType={'copyClipboard'}
|
||||
ariaLabel={'Copy'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const MultipleSizes: Story<void> = () => {
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<CopyToClipboard
|
||||
rawValue={json}
|
||||
text={<p>{'xs size'}</p>}
|
||||
iconType={'copyClipboard'}
|
||||
size={'xs'}
|
||||
ariaLabel={'Copy'}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<CopyToClipboard
|
||||
rawValue={json}
|
||||
text={<p>{'s size'}</p>}
|
||||
iconType={'copyClipboard'}
|
||||
size={'s'}
|
||||
ariaLabel={'Copy'}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<CopyToClipboard
|
||||
rawValue={json}
|
||||
text={<p>{'m size'}</p>}
|
||||
iconType={'copyClipboard'}
|
||||
size={'m'}
|
||||
ariaLabel={'Copy'}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const ButtonOnly: Story<void> = () => {
|
||||
return (
|
||||
<CopyToClipboard
|
||||
rawValue={json}
|
||||
modifier={(value) => {
|
||||
window.alert('modifier');
|
||||
return value;
|
||||
}}
|
||||
iconType={'copyClipboard'}
|
||||
ariaLabel={'Copy'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomColor: Story<void> = () => {
|
||||
return (
|
||||
<CopyToClipboard
|
||||
rawValue={json}
|
||||
modifier={(value) => {
|
||||
window.alert('modifier');
|
||||
return value;
|
||||
}}
|
||||
iconType={'copyClipboard'}
|
||||
ariaLabel={'Copy'}
|
||||
text={<p>{'showing custom color'}</p>}
|
||||
color={'accent'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomIcon: Story<void> = () => {
|
||||
return (
|
||||
<CopyToClipboard
|
||||
rawValue={json}
|
||||
modifier={(value) => {
|
||||
window.alert('modifier');
|
||||
return value;
|
||||
}}
|
||||
iconType={'share'}
|
||||
ariaLabel={'Share'}
|
||||
text={<p>{'custom icon'}</p>}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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(
|
||||
<IntlProvider locale="en">
|
||||
<CopyToClipboard {...props} />
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
describe('ShareButton', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render the copy to clipboard button', () => {
|
||||
const text = 'text';
|
||||
|
||||
const props = {
|
||||
rawValue: 'rawValue',
|
||||
text: <span>{text}</span>,
|
||||
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: <span>{'text'}</span>,
|
||||
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);
|
||||
});
|
||||
});
|
|
@ -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<CopyToClipboardProps> = ({
|
||||
rawValue,
|
||||
modifier,
|
||||
text,
|
||||
iconType,
|
||||
size = 'm',
|
||||
color = 'primary',
|
||||
ariaLabel,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
return (
|
||||
<EuiCopy textToCopy={rawValue}>
|
||||
{(copy) => (
|
||||
<EuiButtonEmpty
|
||||
onClick={() => {
|
||||
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}
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
</EuiCopy>
|
||||
);
|
||||
};
|
||||
|
||||
CopyToClipboard.displayName = 'CopyToClipboard';
|
|
@ -105,7 +105,7 @@ export const FlyoutNavigation: FC<PanelNavigationProps> = memo(
|
|||
responsive={false}
|
||||
css={css`
|
||||
padding-left: ${euiTheme.size.s};
|
||||
padding-right: ${euiTheme.size.l};
|
||||
padding-right: ${euiTheme.size.xl};
|
||||
height: ${euiTheme.size.xxl};
|
||||
`}
|
||||
>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue