diff --git a/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx index 0664608d73c27..9d72af3109564 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ import { createMemoryHistory, History as HistoryPackageHistoryInterface } from 'history'; -import copy from 'copy-to-clipboard'; import { noAncestorsTwoChildrenWithRelatedEventsOnOrigin } from '../data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin'; import { Simulator } from '../test_utilities/simulator'; // Extend jest with a custom matcher @@ -14,10 +13,6 @@ import { urlSearch } from '../test_utilities/url_search'; // the resolver component instance ID, used by the react code to distinguish piece of global state from those used by other resolver instances const resolverComponentInstanceID = 'resolverComponentInstanceID'; -jest.mock('copy-to-clipboard', () => { - return jest.fn(); -}); - describe(`Resolver: when analyzing a tree with no ancestors and two children and two related registry event on the origin, and when the component instance ID is ${resolverComponentInstanceID}`, () => { /** * Get (or lazily create and get) the simulator. @@ -121,8 +116,8 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and copyableFields?.map((copyableField) => { copyableField.simulate('mouseenter'); - simulator().testSubject('clipboard').last().simulate('click'); - expect(copy).toHaveBeenLastCalledWith(copyableField.text(), expect.any(Object)); + simulator().testSubject('resolver:panel:clipboard').last().simulate('click'); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith(copyableField.text()); copyableField.simulate('mouseleave'); }); }); @@ -179,8 +174,8 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and copyableFields?.map((copyableField) => { copyableField.simulate('mouseenter'); - simulator().testSubject('clipboard').last().simulate('click'); - expect(copy).toHaveBeenLastCalledWith(copyableField.text(), expect.any(Object)); + simulator().testSubject('resolver:panel:clipboard').last().simulate('click'); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith(copyableField.text()); copyableField.simulate('mouseleave'); }); }); @@ -288,8 +283,8 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and copyableFields?.map((copyableField) => { copyableField.simulate('mouseenter'); - simulator().testSubject('clipboard').last().simulate('click'); - expect(copy).toHaveBeenLastCalledWith(copyableField.text(), expect.any(Object)); + simulator().testSubject('resolver:panel:clipboard').last().simulate('click'); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith(copyableField.text()); copyableField.simulate('mouseleave'); }); }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/copyable_panel_field.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/copyable_panel_field.tsx index c3474a7724dee..f6a585ea566bb 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/copyable_panel_field.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/copyable_panel_field.tsx @@ -6,11 +6,11 @@ /* eslint-disable react/display-name */ -import { EuiToolTip, EuiPopover } from '@elastic/eui'; +import { EuiToolTip, EuiButtonIcon, EuiPopover } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; -import React, { memo, useState } from 'react'; -import { WithCopyToClipboard } from '../../../common/lib/clipboard/with_copy_to_clipboard'; +import React, { memo, useState, useCallback } from 'react'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { useColors } from '../use_colors'; import { StyledPanel } from '../styles'; @@ -43,8 +43,10 @@ export const CopyablePanelField = memo( ({ textToCopy, content }: { textToCopy: string; content: JSX.Element | string }) => { const { linkColor, copyableFieldBackground } = useColors(); const [isOpen, setIsOpen] = useState(false); + const toasts = useKibana().services.notifications?.toasts; const onMouseEnter = () => setIsOpen(true); + const onMouseLeave = () => setIsOpen(false); const ButtonContent = memo(() => ( )); - const onMouseLeave = () => setIsOpen(false); + const onClick = useCallback( + async (event: React.MouseEvent) => { + try { + await navigator.clipboard.writeText(textToCopy); + } catch (error) { + if (toasts) { + toasts.addError(error, { + title: i18n.translate('xpack.securitySolution.resolver.panel.copyFailureTitle', { + defaultMessage: 'Copy Failure', + }), + }); + } + } + }, + [textToCopy, toasts] + ); return (
@@ -74,10 +91,14 @@ export const CopyablePanelField = memo( defaultMessage: 'Copy to Clipboard', })} > - diff --git a/x-pack/plugins/security_solution/public/resolver/view/side_effect_simulator_factory.ts b/x-pack/plugins/security_solution/public/resolver/view/side_effect_simulator_factory.ts index 25be222e2fe4a..8517459b8aba3 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/side_effect_simulator_factory.ts +++ b/x-pack/plugins/security_solution/public/resolver/view/side_effect_simulator_factory.ts @@ -66,6 +66,18 @@ export const sideEffectSimulatorFactory: () => SideEffectSimulator = () => { return contentRectForElement(this); }); + /** + * Mock the global writeText method as it is not available in jsDOM and alows us to track what was copied + */ + const MockClipboard: Clipboard = { + writeText: jest.fn(), + readText: jest.fn(), + addEventListener: jest.fn(), + dispatchEvent: jest.fn(), + removeEventListener: jest.fn(), + }; + // @ts-ignore navigator doesn't natively exist on global + global.navigator.clipboard = MockClipboard; /** * A mock implementation of `ResizeObserver` that works with our fake `getBoundingClientRect` and `simulateElementResize` */