mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Cases] Copy file hash from within the files table (#172450)
## Summary
The files table allows copying a file's hash(MD5, SHA1, or SHA256) when
available.
We only recently opted in for the hashing of uploaded files so
previously uploaded files will not display the Copy to Clipboard button.
The activity feed in a case's detail view will not display this action.
4bb2ce33
-f999-4d7f-b2c7-f224bb42a162
## Release Notes
Users can copy to the clipboard the hashes of files uploaded to cases.
This commit is contained in:
parent
72142bc978
commit
edf4f35152
8 changed files with 538 additions and 53 deletions
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
* 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 { screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import type { AppMockRenderer } from '../../common/mock';
|
||||
import {
|
||||
buildCasesPermissions,
|
||||
createAppMockRenderer,
|
||||
mockedTestProvidersOwner,
|
||||
} from '../../common/mock';
|
||||
import { constructFileKindIdByOwner } from '../../../common/files';
|
||||
|
||||
import { basicCaseId, basicFileMock } from '../../containers/mock';
|
||||
import { FileActionsPopoverButton } from './file_actions_popover_button';
|
||||
import { useDeleteFileAttachment } from '../../containers/use_delete_file_attachment';
|
||||
|
||||
jest.mock('../../containers/use_delete_file_attachment');
|
||||
|
||||
const useDeleteFileAttachmentMock = useDeleteFileAttachment as jest.Mock;
|
||||
|
||||
describe('FileActionsPopoverButton', () => {
|
||||
let appMockRender: AppMockRenderer;
|
||||
const mutate = jest.fn();
|
||||
|
||||
useDeleteFileAttachmentMock.mockReturnValue({ isLoading: false, mutate });
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
appMockRender = createAppMockRenderer();
|
||||
});
|
||||
|
||||
it('renders file actions popover button correctly', async () => {
|
||||
appMockRender.render(<FileActionsPopoverButton caseId={basicCaseId} theFile={basicFileMock} />);
|
||||
|
||||
expect(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('clicking the button opens the popover', async () => {
|
||||
appMockRender.render(<FileActionsPopoverButton caseId={basicCaseId} theFile={basicFileMock} />);
|
||||
|
||||
userEvent.click(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByTestId(`cases-files-popover-${basicFileMock.id}`)
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('cases-files-delete-button')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('cases-files-download-button')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('cases-files-copy-hash-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render the copy hash button if the file has no hashes', async () => {
|
||||
appMockRender.render(
|
||||
<FileActionsPopoverButton caseId={basicCaseId} theFile={{ ...basicFileMock, hash: {} }} />
|
||||
);
|
||||
|
||||
userEvent.click(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByTestId(`cases-files-popover-${basicFileMock.id}`)
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(await screen.queryByTestId('cases-files-copy-hash-button')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('clicking the copy file hash button rerenders the popover correctly', async () => {
|
||||
appMockRender.render(<FileActionsPopoverButton caseId={basicCaseId} theFile={basicFileMock} />);
|
||||
|
||||
const popoverButton = await screen.findByTestId(
|
||||
`cases-files-actions-popover-button-${basicFileMock.id}`
|
||||
);
|
||||
|
||||
expect(popoverButton).toBeInTheDocument();
|
||||
userEvent.click(popoverButton);
|
||||
|
||||
expect(
|
||||
await screen.findByTestId(`cases-files-popover-${basicFileMock.id}`)
|
||||
).toBeInTheDocument();
|
||||
|
||||
const copyFileHashButton = await screen.findByTestId('cases-files-copy-hash-button');
|
||||
|
||||
expect(copyFileHashButton).toBeInTheDocument();
|
||||
|
||||
userEvent.click(copyFileHashButton);
|
||||
|
||||
expect(await screen.findByTestId('cases-files-copy-md5-hash-button')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('cases-files-copy-sha1-hash-button')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('cases-files-copy-sha256-hash-button')).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
(
|
||||
await within(await screen.findByTestId('cases-files-popover-context-menu')).findAllByRole(
|
||||
'button'
|
||||
)
|
||||
).length
|
||||
).toBe(7);
|
||||
});
|
||||
|
||||
describe('copy file hashes', () => {
|
||||
const originalClipboard = global.window.navigator.clipboard;
|
||||
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
value: {
|
||||
writeText: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
},
|
||||
writable: true,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
value: originalClipboard,
|
||||
});
|
||||
});
|
||||
|
||||
it('clicking copy md5 file hash copies the hash to the clipboard', async () => {
|
||||
appMockRender.render(
|
||||
<FileActionsPopoverButton caseId={basicCaseId} theFile={basicFileMock} />
|
||||
);
|
||||
|
||||
userEvent.click(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('cases-files-copy-hash-button'), undefined, {
|
||||
skipPointerEventsCheck: true,
|
||||
});
|
||||
userEvent.click(await screen.findByTestId('cases-files-copy-md5-hash-button'), undefined, {
|
||||
skipPointerEventsCheck: true,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(basicFileMock.hash?.md5);
|
||||
});
|
||||
});
|
||||
|
||||
it('clicking copy SHA1 file hash copies the hash to the clipboard', async () => {
|
||||
appMockRender.render(
|
||||
<FileActionsPopoverButton caseId={basicCaseId} theFile={basicFileMock} />
|
||||
);
|
||||
|
||||
userEvent.click(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('cases-files-copy-hash-button'), undefined, {
|
||||
skipPointerEventsCheck: true,
|
||||
});
|
||||
userEvent.click(await screen.findByTestId('cases-files-copy-sha1-hash-button'), undefined, {
|
||||
skipPointerEventsCheck: true,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(basicFileMock.hash?.sha1);
|
||||
});
|
||||
});
|
||||
|
||||
it('clicking copy SHA256 file hash copies the hash to the clipboard', async () => {
|
||||
appMockRender.render(
|
||||
<FileActionsPopoverButton caseId={basicCaseId} theFile={basicFileMock} />
|
||||
);
|
||||
|
||||
userEvent.click(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('cases-files-copy-hash-button'), undefined, {
|
||||
skipPointerEventsCheck: true,
|
||||
});
|
||||
userEvent.click(await screen.findByTestId('cases-files-copy-sha256-hash-button'), undefined, {
|
||||
skipPointerEventsCheck: true,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(basicFileMock.hash?.sha256);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete button', () => {
|
||||
it('clicking delete button opens the confirmation modal', async () => {
|
||||
appMockRender.render(
|
||||
<FileActionsPopoverButton caseId={basicCaseId} theFile={basicFileMock} />
|
||||
);
|
||||
|
||||
userEvent.click(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('cases-files-delete-button'), undefined, {
|
||||
skipPointerEventsCheck: true,
|
||||
});
|
||||
|
||||
expect(await screen.findByTestId('property-actions-confirm-modal')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('clicking delete button in the confirmation modal calls deleteFileAttachment with proper params', async () => {
|
||||
appMockRender.render(
|
||||
<FileActionsPopoverButton caseId={basicCaseId} theFile={basicFileMock} />
|
||||
);
|
||||
|
||||
userEvent.click(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('cases-files-delete-button'), undefined, {
|
||||
skipPointerEventsCheck: true,
|
||||
});
|
||||
|
||||
expect(await screen.findByTestId('property-actions-confirm-modal')).toBeInTheDocument();
|
||||
|
||||
userEvent.click(await screen.findByTestId('confirmModalConfirmButton'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mutate).toHaveBeenCalledTimes(1);
|
||||
expect(mutate).toHaveBeenCalledWith({
|
||||
caseId: basicCaseId,
|
||||
fileId: basicFileMock.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('delete button is not rendered if user has no delete permission', async () => {
|
||||
appMockRender = createAppMockRenderer({
|
||||
permissions: buildCasesPermissions({ delete: false }),
|
||||
});
|
||||
|
||||
appMockRender.render(
|
||||
<FileActionsPopoverButton caseId={basicCaseId} theFile={basicFileMock} />
|
||||
);
|
||||
|
||||
userEvent.click(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId('cases-files-delete-button')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('download button', () => {
|
||||
it('renders download button with correct href', async () => {
|
||||
appMockRender.render(
|
||||
<FileActionsPopoverButton caseId={basicCaseId} theFile={basicFileMock} />
|
||||
);
|
||||
|
||||
userEvent.click(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId('cases-files-download-button')).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(appMockRender.getFilesClient().getDownloadHref).toBeCalled();
|
||||
expect(appMockRender.getFilesClient().getDownloadHref).toHaveBeenCalledWith({
|
||||
fileKind: constructFileKindIdByOwner(mockedTestProvidersOwner[0]),
|
||||
id: basicFileMock.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* 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, { useCallback, useMemo, useState } from 'react';
|
||||
import type {
|
||||
EuiContextMenuPanelDescriptor,
|
||||
EuiContextMenuPanelItemDescriptor,
|
||||
} from '@elastic/eui';
|
||||
import { EuiButtonIcon, EuiPopover, EuiContextMenu, EuiIcon, EuiTextColor } from '@elastic/eui';
|
||||
import type { FileJSON } from '@kbn/shared-ux-file-types';
|
||||
import { useFilesContext } from '@kbn/shared-ux-file-context';
|
||||
import type { Owner } from '../../../common/constants/types';
|
||||
import * as i18n from './translations';
|
||||
|
||||
import { constructFileKindIdByOwner } from '../../../common/files';
|
||||
import { useCasesContext } from '../cases_context/use_cases_context';
|
||||
import { useCasesToast } from '../../common/use_cases_toast';
|
||||
import { DeleteAttachmentConfirmationModal } from '../user_actions/delete_attachment_confirmation_modal';
|
||||
import { useDeleteFileAttachment } from '../../containers/use_delete_file_attachment';
|
||||
import { useDeletePropertyAction } from '../user_actions/property_actions/use_delete_property_action';
|
||||
|
||||
export const FileActionsPopoverButton: React.FC<{ caseId: string; theFile: FileJSON }> = ({
|
||||
caseId,
|
||||
theFile,
|
||||
}) => {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const { owner, permissions } = useCasesContext();
|
||||
const { client: filesClient } = useFilesContext();
|
||||
|
||||
const { showSuccessToast } = useCasesToast();
|
||||
const { isLoading, mutate: deleteFileAttachment } = useDeleteFileAttachment();
|
||||
const { showDeletionModal, onModalOpen, onConfirm, onCancel } = useDeletePropertyAction({
|
||||
onDelete: () => deleteFileAttachment({ caseId, fileId: theFile.id }),
|
||||
});
|
||||
|
||||
const tooglePopover = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]);
|
||||
const closePopover = useCallback(() => setIsPopoverOpen(false), []);
|
||||
|
||||
const panels = useMemo((): EuiContextMenuPanelDescriptor[] => {
|
||||
const fileHashesAvailable = theFile.hash?.md5 || theFile.hash?.sha1 || theFile.hash?.sha256;
|
||||
const mainPanelItems: EuiContextMenuPanelItemDescriptor[] = [
|
||||
{
|
||||
name: i18n.DOWNLOAD_FILE,
|
||||
icon: 'download',
|
||||
href: filesClient.getDownloadHref({
|
||||
fileKind: constructFileKindIdByOwner(owner[0] as Owner),
|
||||
id: theFile.id,
|
||||
}),
|
||||
onClick: closePopover,
|
||||
'data-test-subj': 'cases-files-download-button',
|
||||
},
|
||||
];
|
||||
|
||||
const panelsToBuild = [
|
||||
{
|
||||
id: 0,
|
||||
title: i18n.ACTIONS,
|
||||
items: mainPanelItems,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: i18n.COPY_FILE_HASH,
|
||||
items: [
|
||||
{
|
||||
name: 'MD5',
|
||||
icon: 'copyClipboard',
|
||||
disabled: !theFile.hash?.md5,
|
||||
onClick: () => {
|
||||
if (theFile.hash?.md5) {
|
||||
navigator.clipboard.writeText(theFile.hash.md5).then(() => {
|
||||
closePopover();
|
||||
showSuccessToast(i18n.COPY_FILE_HASH_SUCCESS('md5'));
|
||||
});
|
||||
}
|
||||
},
|
||||
'data-test-subj': 'cases-files-copy-md5-hash-button',
|
||||
},
|
||||
{
|
||||
name: 'SHA1',
|
||||
icon: 'copyClipboard',
|
||||
disabled: !theFile.hash?.sha1,
|
||||
onClick: () => {
|
||||
if (theFile.hash?.sha1) {
|
||||
navigator.clipboard.writeText(theFile.hash.sha1).then(() => {
|
||||
closePopover();
|
||||
showSuccessToast(i18n.COPY_FILE_HASH_SUCCESS('sha1'));
|
||||
});
|
||||
}
|
||||
},
|
||||
'data-test-subj': 'cases-files-copy-sha1-hash-button',
|
||||
},
|
||||
{
|
||||
name: 'SHA256',
|
||||
icon: 'copyClipboard',
|
||||
disabled: !theFile.hash?.sha256,
|
||||
onClick: () => {
|
||||
if (theFile.hash?.sha256) {
|
||||
navigator.clipboard.writeText(theFile.hash.sha256).then(() => {
|
||||
closePopover();
|
||||
showSuccessToast(i18n.COPY_FILE_HASH_SUCCESS('sha256'));
|
||||
});
|
||||
}
|
||||
},
|
||||
'data-test-subj': 'cases-files-copy-sha256-hash-button',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
if (fileHashesAvailable) {
|
||||
mainPanelItems.push({
|
||||
name: i18n.COPY_FILE_HASH,
|
||||
icon: 'copyClipboard',
|
||||
panel: 1,
|
||||
'data-test-subj': 'cases-files-copy-hash-button',
|
||||
});
|
||||
}
|
||||
|
||||
if (permissions.delete) {
|
||||
mainPanelItems.push({
|
||||
name: <EuiTextColor color={'danger'}>{i18n.DELETE_FILE}</EuiTextColor>,
|
||||
icon: <EuiIcon type="trash" size="m" color={'danger'} />,
|
||||
onClick: () => {
|
||||
closePopover();
|
||||
onModalOpen();
|
||||
},
|
||||
disabled: isLoading,
|
||||
'data-test-subj': 'cases-files-delete-button',
|
||||
});
|
||||
}
|
||||
|
||||
return panelsToBuild;
|
||||
}, [
|
||||
closePopover,
|
||||
filesClient,
|
||||
isLoading,
|
||||
onModalOpen,
|
||||
owner,
|
||||
permissions,
|
||||
showSuccessToast,
|
||||
theFile,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPopover
|
||||
id={`cases-files-popover-${theFile.id}`}
|
||||
key={`cases-files-popover-${theFile.id}`}
|
||||
data-test-subj={`cases-files-popover-${theFile.id}`}
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
onClick={tooglePopover}
|
||||
iconType="boxesHorizontal"
|
||||
aria-label={i18n.ACTIONS}
|
||||
color="text"
|
||||
key={`cases-files-actions-popover-button-${theFile.id}`}
|
||||
data-test-subj={`cases-files-actions-popover-button-${theFile.id}`}
|
||||
/>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenu
|
||||
initialPanelId={0}
|
||||
panels={panels}
|
||||
data-test-subj={'cases-files-popover-context-menu'}
|
||||
/>
|
||||
</EuiPopover>
|
||||
{showDeletionModal && (
|
||||
<DeleteAttachmentConfirmationModal
|
||||
title={i18n.DELETE_FILE_TITLE}
|
||||
confirmButtonText={i18n.DELETE}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
FileActionsPopoverButton.displayName = 'FileActionsPopoverButton';
|
|
@ -40,8 +40,9 @@ describe('FilesTable', () => {
|
|||
expect(await screen.findByTestId('cases-files-table-filename')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('cases-files-table-filetype')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('cases-files-table-date-added')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('cases-files-download-button')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('cases-files-delete-button')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders loading state', async () => {
|
||||
|
@ -132,8 +133,12 @@ describe('FilesTable', () => {
|
|||
it('download button renders correctly', async () => {
|
||||
appMockRender.render(<FilesTable {...defaultProps} />);
|
||||
|
||||
userEvent.click(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(appMockRender.getFilesClient().getDownloadHref).toBeCalledTimes(1);
|
||||
expect(appMockRender.getFilesClient().getDownloadHref).toBeCalled();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
|
@ -149,16 +154,9 @@ describe('FilesTable', () => {
|
|||
it('delete button renders correctly', async () => {
|
||||
appMockRender.render(<FilesTable {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(appMockRender.getFilesClient().getDownloadHref).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(appMockRender.getFilesClient().getDownloadHref).toHaveBeenCalledWith({
|
||||
fileKind: constructFileKindIdByOwner(mockedTestProvidersOwner[0]),
|
||||
id: basicFileMock.id,
|
||||
});
|
||||
});
|
||||
userEvent.click(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId('cases-files-delete-button')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -166,26 +164,40 @@ describe('FilesTable', () => {
|
|||
it('clicking delete button opens deletion modal', async () => {
|
||||
appMockRender.render(<FilesTable {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(appMockRender.getFilesClient().getDownloadHref).toBeCalledTimes(1);
|
||||
});
|
||||
userEvent.click(
|
||||
await screen.findByTestId(`cases-files-actions-popover-button-${basicFileMock.id}`)
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(appMockRender.getFilesClient().getDownloadHref).toHaveBeenCalledWith({
|
||||
fileKind: constructFileKindIdByOwner(mockedTestProvidersOwner[0]),
|
||||
id: basicFileMock.id,
|
||||
});
|
||||
});
|
||||
|
||||
const deleteButton = await screen.findByTestId('cases-files-delete-button');
|
||||
|
||||
expect(deleteButton).toBeInTheDocument();
|
||||
|
||||
userEvent.click(deleteButton);
|
||||
userEvent.click(await screen.findByTestId('cases-files-delete-button'));
|
||||
|
||||
expect(await screen.findByTestId('property-actions-confirm-modal')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('clicking the copy file hash button rerenders the popover correctly', async () => {
|
||||
appMockRender.render(<FilesTable {...defaultProps} />);
|
||||
|
||||
const popoverButton = await screen.findByTestId(
|
||||
`cases-files-actions-popover-button-${basicFileMock.id}`
|
||||
);
|
||||
|
||||
expect(popoverButton).toBeInTheDocument();
|
||||
userEvent.click(popoverButton);
|
||||
|
||||
expect(
|
||||
await screen.findByTestId(`cases-files-popover-${basicFileMock.id}`)
|
||||
).toBeInTheDocument();
|
||||
|
||||
const copyFileHashButton = await screen.findByTestId('cases-files-copy-hash-button');
|
||||
|
||||
expect(copyFileHashButton).toBeInTheDocument();
|
||||
|
||||
userEvent.click(copyFileHashButton);
|
||||
|
||||
expect(await screen.findByTestId('cases-files-copy-md5-hash-button')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('cases-files-copy-sha1-hash-button')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('cases-files-copy-sha256-hash-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('go to next page calls onTableChange with correct values', async () => {
|
||||
const mockPagination = { pageIndex: 0, pageSize: 1, totalItemCount: 2 };
|
||||
|
||||
|
|
|
@ -31,6 +31,16 @@ export const DOWNLOAD_FILE = i18n.translate('xpack.cases.caseView.files.download
|
|||
defaultMessage: 'Download file',
|
||||
});
|
||||
|
||||
export const COPY_FILE_HASH = i18n.translate('xpack.cases.caseView.files.copyFileHash', {
|
||||
defaultMessage: 'Copy file hash',
|
||||
});
|
||||
|
||||
export const COPY_FILE_HASH_SUCCESS = (hashName: string) =>
|
||||
i18n.translate('xpack.cases.caseView.files.copyFileHashSuccess', {
|
||||
values: { hashName },
|
||||
defaultMessage: `Copied {hashName} file hash successfully`,
|
||||
});
|
||||
|
||||
export const FILES_TABLE = i18n.translate('xpack.cases.caseView.files.filesTable', {
|
||||
defaultMessage: 'Files table',
|
||||
});
|
||||
|
|
|
@ -52,15 +52,7 @@ describe('useFilesTableColumns', () => {
|
|||
Object {
|
||||
"actions": Array [
|
||||
Object {
|
||||
"description": "Download file",
|
||||
"isPrimary": true,
|
||||
"name": "Download",
|
||||
"render": [Function],
|
||||
},
|
||||
Object {
|
||||
"description": "Delete file",
|
||||
"isPrimary": true,
|
||||
"name": "Delete",
|
||||
"name": "Actions",
|
||||
"render": [Function],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -13,8 +13,7 @@ import type { FileJSON } from '@kbn/shared-ux-file-types';
|
|||
import * as i18n from './translations';
|
||||
import { parseMimeType } from './utils';
|
||||
import { FileNameLink } from './file_name_link';
|
||||
import { FileDownloadButton } from './file_download_button';
|
||||
import { FileDeleteButton } from './file_delete_button';
|
||||
import { FileActionsPopoverButton } from './file_actions_popover_button';
|
||||
|
||||
export interface FilesTableColumnsProps {
|
||||
caseId: string;
|
||||
|
@ -52,17 +51,9 @@ export const useFilesTableColumns = ({
|
|||
width: '120px',
|
||||
actions: [
|
||||
{
|
||||
name: 'Download',
|
||||
isPrimary: true,
|
||||
description: i18n.DOWNLOAD_FILE,
|
||||
render: (file: FileJSON) => <FileDownloadButton fileId={file.id} isIcon={true} />,
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
isPrimary: true,
|
||||
description: i18n.DELETE_FILE,
|
||||
render: (file: FileJSON) => (
|
||||
<FileDeleteButton caseId={caseId} fileId={file.id} isIcon={true} />
|
||||
name: i18n.ACTIONS,
|
||||
render: (theFile: FileJSON) => (
|
||||
<FileActionsPopoverButton caseId={caseId} theFile={theFile} />
|
||||
),
|
||||
},
|
||||
],
|
||||
|
|
|
@ -265,6 +265,11 @@ export const basicFileMock: FileJSON = {
|
|||
fileKind: '',
|
||||
status: 'READY',
|
||||
extension: 'png',
|
||||
hash: {
|
||||
md5: 'md5',
|
||||
sha1: 'sha1',
|
||||
sha256: 'sha256',
|
||||
},
|
||||
};
|
||||
|
||||
export const caseWithAlerts = {
|
||||
|
|
|
@ -39,10 +39,23 @@ export function CasesFilesTableServiceProvider({ getService, getPageObject }: Ft
|
|||
await searchField.pressKeys(browser.keys.ENTER);
|
||||
},
|
||||
|
||||
async deleteFile(index: number = 0) {
|
||||
const row = await this.getFileByIndex(index);
|
||||
async openActionsPopover(index: number = 0) {
|
||||
const popoverButtons = await find.allByCssSelector(
|
||||
'[data-test-subj*="cases-files-actions-popover-button-"',
|
||||
100
|
||||
);
|
||||
|
||||
(await row.findByCssSelector('[data-test-subj="cases-files-delete-button"]')).click();
|
||||
assertFileExists(index, popoverButtons.length);
|
||||
|
||||
popoverButtons[index].click();
|
||||
|
||||
await testSubjects.existOrFail('contextMenuPanelTitle');
|
||||
},
|
||||
|
||||
async deleteFile(index: number = 0) {
|
||||
await this.openActionsPopover(index);
|
||||
|
||||
(await testSubjects.find('cases-files-delete-button', 1000)).click();
|
||||
|
||||
await testSubjects.click('confirmModalConfirmButton');
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue