[ResponseOps] Add Copy Id button for MW (#209135)

Resolve: https://github.com/elastic/kibana/issues/203569

Solution looks like:

https://github.com/user-attachments/assets/424c6518-289b-4119-b909-4e589a618069

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Julia 2025-02-19 09:07:38 +01:00 committed by GitHub
parent ce609e8420
commit ae9971b8b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 84 additions and 6 deletions

View file

@ -7,11 +7,29 @@
import { fireEvent } from '@testing-library/react';
import React from 'react';
import userEvent from '@testing-library/user-event';
import { AppMockRenderer, createAppMockRenderer } from '../../../lib/test_utils';
import { TableActionsPopover } from './table_actions_popover';
import { MaintenanceWindowStatus } from '../../../../common';
const mockAddSuccess = jest.fn();
jest.mock('../../../utils/kibana_react', () => {
const originalModule = jest.requireActual('../../../utils/kibana_react');
return {
...originalModule,
useKibana: () => {
const { services } = originalModule.useKibana();
return {
services: {
...services,
notifications: { toasts: { addSuccess: mockAddSuccess } },
},
};
},
};
});
describe('TableActionsPopover', () => {
let appMockRenderer: AppMockRenderer;
@ -103,4 +121,33 @@ describe('TableActionsPopover', () => {
fireEvent.click(result.getByTestId('table-actions-icon-button'));
expect(result.getByTestId('table-actions-unarchive')).toBeInTheDocument();
});
test('it shows the success toast when maintenance window id is copied', async () => {
Object.assign(navigator, {
clipboard: {
writeText: jest.fn().mockResolvedValue(''),
},
});
const result = appMockRenderer.render(
<TableActionsPopover
id={'123'}
isLoading={false}
status={MaintenanceWindowStatus.Archived}
onEdit={() => {}}
onCancel={() => {}}
onArchive={() => {}}
onCancelAndArchive={() => {}}
/>
);
await userEvent.click(await result.findByTestId('table-actions-icon-button'));
expect(await result.findByTestId('table-actions-copy-id')).toBeInTheDocument();
await userEvent.click(await result.findByTestId('table-actions-copy-id'));
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('123');
expect(mockAddSuccess).toBeCalledWith('Copied maintenance window ID to clipboard');
Object.assign(navigator, global.window.navigator.clipboard);
});
});

View file

@ -17,6 +17,7 @@ import {
} from '@elastic/eui';
import * as i18n from '../translations';
import { MaintenanceWindowStatus } from '../../../../common';
import { useKibana } from '../../../utils/kibana_react';
export interface TableActionsPopoverProps {
id: string;
@ -28,7 +29,7 @@ export interface TableActionsPopoverProps {
onCancelAndArchive: (id: string) => void;
}
type ModalType = 'cancel' | 'cancelAndArchive' | 'archive' | 'unarchive';
type ActionType = ModalType | 'edit';
type ActionType = ModalType | 'edit' | 'copyId';
export const TableActionsPopover: React.FC<TableActionsPopoverProps> = React.memo(
({ id, status, isLoading, onEdit, onCancel, onArchive, onCancelAndArchive }) => {
@ -36,6 +37,10 @@ export const TableActionsPopover: React.FC<TableActionsPopoverProps> = React.mem
const [isModalVisible, setIsModalVisible] = useState(false);
const [modalType, setModalType] = useState<ModalType>();
const {
notifications: { toasts },
} = useKibana().services;
const onButtonClick = useCallback(() => {
setIsPopoverOpen((open) => !open);
}, []);
@ -133,6 +138,21 @@ export const TableActionsPopover: React.FC<TableActionsPopoverProps> = React.mem
{i18n.TABLE_ACTION_EDIT}
</EuiContextMenuItem>
),
copyId: (
<EuiContextMenuItem
data-test-subj="table-actions-copy-id"
key="copy-id"
icon="copyClipboard"
onClick={() => {
closePopover();
navigator.clipboard.writeText(id).then(() => {
toasts.addSuccess(i18n.COPY_ID_ACTION_SUCCESS);
});
}}
>
{i18n.COPY_ID}
</EuiContextMenuItem>
),
cancel: (
<EuiContextMenuItem
data-test-subj="table-actions-cancel"
@ -187,13 +207,13 @@ export const TableActionsPopover: React.FC<TableActionsPopoverProps> = React.mem
),
};
const statusMenuItemsMap: Record<MaintenanceWindowStatus, ActionType[]> = {
running: ['edit', 'cancel', 'cancelAndArchive'],
upcoming: ['edit', 'archive'],
finished: ['edit', 'archive'],
archived: ['unarchive'],
running: ['edit', 'copyId', 'cancel', 'cancelAndArchive'],
upcoming: ['edit', 'copyId', 'archive'],
finished: ['edit', 'copyId', 'archive'],
archived: ['copyId', 'unarchive'],
};
return statusMenuItemsMap[status].map((type) => menuItems[type]);
}, [id, status, onEdit, closePopover, showModal]);
}, [status, closePopover, onEdit, id, toasts, showModal]);
const button = useMemo(
() => (

View file

@ -630,6 +630,17 @@ export const ARCHIVE = i18n.translate('xpack.alerting.maintenanceWindows.archive
defaultMessage: 'Archive',
});
export const COPY_ID = i18n.translate('xpack.alerting.maintenanceWindows.copyId', {
defaultMessage: 'Copy ID',
});
export const COPY_ID_ACTION_SUCCESS = i18n.translate(
'xpack.alerting.maintenanceWindows.copyId.success',
{
defaultMessage: 'Copied maintenance window ID to clipboard',
}
);
export const ARCHIVE_TITLE = i18n.translate('xpack.alerting.maintenanceWindows.archive.title', {
defaultMessage: 'Archive maintenance window',
});