diff --git a/public/locales/en/files.json b/public/locales/en/files.json index 662cbbfb6..3e8938625 100644 --- a/public/locales/en/files.json +++ b/public/locales/en/files.json @@ -103,6 +103,11 @@ "message": "This file has already been deleted (or replaced) in the current version. It may not be edited.", "close": "Close" }, + "noSelectedFilesAlert": { + "title": "Select File(s)", + "message": "Please select one or more files.", + "close": "Close" + }, "accessFileMenu": { "title": "Access File", "headers": { @@ -122,11 +127,11 @@ } }, "downloadFiles": { - "title": "Download", - "options": { - "original": "Original Format", - "archival": "Archival Format (.tab)" - } + "title": "Download", + "options": { + "original": "Original Format", + "archival": "Archival Format (.tab)" + } } }, "requestAccess": { diff --git a/src/sections/dataset/dataset-files/files-table/FilesTableColumnsDefinition.tsx b/src/sections/dataset/dataset-files/files-table/FilesTableColumnsDefinition.tsx index 53f985f51..bdf534f62 100644 --- a/src/sections/dataset/dataset-files/files-table/FilesTableColumnsDefinition.tsx +++ b/src/sections/dataset/dataset-files/files-table/FilesTableColumnsDefinition.tsx @@ -6,8 +6,12 @@ import { FileInfoHeader } from './file-info/FileInfoHeader' import { FileActionsHeader } from './file-actions/FileActionsHeader' import { FileActionsCell } from './file-actions/file-actions-cell/FileActionsCell' import { FilePaginationInfo } from '../../../../files/domain/models/FilePaginationInfo' +import { FileSelection } from './row-selection/useFileSelection' -export const createColumnsDefinition = (paginationInfo: FilePaginationInfo): ColumnDef[] => [ +export const createColumnsDefinition = ( + paginationInfo: FilePaginationInfo, + fileSelection: FileSelection +): ColumnDef[] => [ { id: 'select', header: ({ table }) => ( @@ -38,7 +42,10 @@ export const createColumnsDefinition = (paginationInfo: FilePaginationInfo): Col }, { header: ({ table }) => ( - row.original)} /> + row.original)} + fileSelection={fileSelection} + /> ), accessorKey: 'status', cell: (props) => diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/FileActionsHeader.module.scss b/src/sections/dataset/dataset-files/files-table/file-actions/FileActionsHeader.module.scss index 3b4481b8a..7154c087e 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/FileActionsHeader.module.scss +++ b/src/sections/dataset/dataset-files/files-table/file-actions/FileActionsHeader.module.scss @@ -1,5 +1,5 @@ .container { - text-align: right; display: flex; justify-content: end; + text-align: right; } \ No newline at end of file diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/FileActionsHeader.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/FileActionsHeader.tsx index c762d8196..204ad474e 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/FileActionsHeader.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/FileActionsHeader.tsx @@ -3,15 +3,17 @@ import { File } from '../../../../../files/domain/models/File' import styles from './FileActionsHeader.module.scss' import { useTranslation } from 'react-i18next' import { DownloadFilesButton } from './download-files/DownloadFilesButton' +import { FileSelection } from '../row-selection/useFileSelection' interface FileActionsHeaderProps { files: File[] + fileSelection: FileSelection } -export function FileActionsHeader({ files }: FileActionsHeaderProps) { +export function FileActionsHeader({ files, fileSelection }: FileActionsHeaderProps) { const { t } = useTranslation('files') return (
- - + +
) } diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx index e6595a578..4bf4bbbed 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx @@ -4,15 +4,21 @@ import { Button, DropdownButton, DropdownButtonItem } from '@iqss/dataverse-desi import { Download } from 'react-bootstrap-icons' import styles from './DownloadFilesButton.module.scss' import { useTranslation } from 'react-i18next' +import { FileSelection } from '../../row-selection/useFileSelection' +import { NoSelectedFilesModal } from '../no-selected-files-modal/NoSelectedFilesModal' +import { useState } from 'react' interface DownloadFilesButtonProps { files: File[] + fileSelection: FileSelection } const MINIMUM_FILES_COUNT_TO_SHOW_DOWNLOAD_FILES_BUTTON = 1 -export function DownloadFilesButton({ files }: DownloadFilesButtonProps) { +const SELECTED_FILES_EMPTY = 0 +export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButtonProps) { const { t } = useTranslation('files') const { dataset } = useDataset() + const [showNoFilesSelectedModal, setShowNoFilesSelectedModal] = useState(false) if ( files.length < MINIMUM_FILES_COUNT_TO_SHOW_DOWNLOAD_FILES_BUTTON || @@ -21,23 +27,49 @@ export function DownloadFilesButton({ files }: DownloadFilesButtonProps) { return <> } + const onClick = () => { + if (Object.keys(fileSelection).length === SELECTED_FILES_EMPTY) { + setShowNoFilesSelectedModal(true) + } + } + if (files.some((file) => file.isTabularData)) { return ( - } - title={t('actions.downloadFiles.title')} - variant="secondary" - withSpacing> - {t('actions.downloadFiles.options.original')} - {t('actions.downloadFiles.options.archival')} - + <> + } + title={t('actions.downloadFiles.title')} + variant="secondary" + withSpacing> + + {t('actions.downloadFiles.options.original')} + + + {t('actions.downloadFiles.options.archival')} + + + setShowNoFilesSelectedModal(false)} + /> + ) } return ( - + <> + + setShowNoFilesSelectedModal(false)} + /> + ) } diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.tsx index c255662df..052d5957e 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.tsx @@ -6,12 +6,14 @@ import { EditFilesOptions } from './EditFilesOptions' import { File } from '../../../../../../files/domain/models/File' import { useTranslation } from 'react-i18next' import { useDataset } from '../../../../DatasetContext' +import { FileSelection } from '../../row-selection/useFileSelection' interface EditFilesMenuProps { files: File[] + fileSelection: FileSelection } const MINIMUM_FILES_COUNT_TO_SHOW_EDIT_FILES_BUTTON = 1 -export function EditFilesMenu({ files }: EditFilesMenuProps) { +export function EditFilesMenu({ files, fileSelection }: EditFilesMenuProps) { const { t } = useTranslation('files') const { user } = useSession() const { dataset } = useDataset() @@ -30,7 +32,7 @@ export function EditFilesMenu({ files }: EditFilesMenuProps) { title={t('actions.editFilesMenu.title')} disabled={dataset.isLockedFromEdits || !dataset.hasValidTermsOfAccess} icon={}> - + ) } diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesOptions.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesOptions.tsx index 8bab46607..1377303f5 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesOptions.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesOptions.tsx @@ -1,32 +1,62 @@ import { DropdownButtonItem } from '@iqss/dataverse-design-system' import { File } from '../../../../../../files/domain/models/File' import { useTranslation } from 'react-i18next' +import { useState } from 'react' +import { FileSelection } from '../../row-selection/useFileSelection' +import { NoSelectedFilesModal } from '../no-selected-files-modal/NoSelectedFilesModal' interface EditFileOptionsProps { files: File[] + fileSelection: FileSelection } -export function EditFilesOptions({ files }: EditFileOptionsProps) { +const SELECTED_FILES_EMPTY = 0 +export function EditFilesOptions({ files, fileSelection }: EditFileOptionsProps) { const { t } = useTranslation('files') + const [showNoFilesSelectedModal, setShowNoFilesSelectedModal] = useState(false) const settingsEmbargoAllowed = false // TODO - Ask Guillermo if this is included in the settings endpoint const provenanceEnabledByConfig = false // TODO - Ask Guillermo if this is included in the MVP and from which endpoint is coming from + const onClick = () => { + if (Object.keys(fileSelection).length === SELECTED_FILES_EMPTY) { + setShowNoFilesSelectedModal(true) + } + } + return ( <> - {t('actions.editFilesMenu.options.metadata')} + + {t('actions.editFilesMenu.options.metadata')} + {files.some((file) => file.access.restricted) && ( - {t('actions.editFilesMenu.options.unrestrict')} + + {t('actions.editFilesMenu.options.unrestrict')} + )} {files.some((file) => !file.access.restricted) && ( - {t('actions.editFilesMenu.options.restrict')} + + {t('actions.editFilesMenu.options.restrict')} + )} - {t('actions.editFilesMenu.options.replace')} + + {t('actions.editFilesMenu.options.replace')} + {settingsEmbargoAllowed && ( - {t('actions.editFilesMenu.options.embargo')} + + {t('actions.editFilesMenu.options.embargo')} + )} {provenanceEnabledByConfig && ( - {t('actions.editFilesMenu.options.provenance')} + + {t('actions.editFilesMenu.options.provenance')} + )} - {t('actions.editFilesMenu.options.delete')} + + {t('actions.editFilesMenu.options.delete')} + + setShowNoFilesSelectedModal(false)} + /> ) } diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/no-selected-files-modal/NoSelectedFilesModal.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/no-selected-files-modal/NoSelectedFilesModal.tsx new file mode 100644 index 000000000..d580b4b8c --- /dev/null +++ b/src/sections/dataset/dataset-files/files-table/file-actions/no-selected-files-modal/NoSelectedFilesModal.tsx @@ -0,0 +1,30 @@ +import { useTranslation } from 'react-i18next' +import { Button, Modal } from '@iqss/dataverse-design-system' +import styles from '../file-actions-cell/file-action-buttons/file-options-menu/FileAlreadyDeletedModal.module.scss' +import { ExclamationCircleFill } from 'react-bootstrap-icons' + +interface NoSelectedFilesModalProps { + show: boolean + handleClose: () => void +} + +export function NoSelectedFilesModal({ show, handleClose }: NoSelectedFilesModalProps) { + const { t } = useTranslation('files') + return ( + + + {t('actions.noSelectedFilesAlert.title')} + + +

+ {t('actions.noSelectedFilesAlert.message')} +

+
+ + + +
+ ) +} diff --git a/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx b/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx index a3e0d6d46..b403e9782 100644 --- a/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx +++ b/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx @@ -21,7 +21,7 @@ export function useFilesTable(files: File[], paginationInfo: FilePaginationInfo) ) const table = useReactTable({ data: files, - columns: createColumnsDefinition(paginationInfo), + columns: createColumnsDefinition(paginationInfo, fileSelection), state: { rowSelection: currentPageRowSelection }, diff --git a/src/stories/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.stories.tsx b/src/stories/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.stories.tsx index 61e22d129..87836ad96 100644 --- a/src/stories/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.stories.tsx +++ b/src/stories/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.stories.tsx @@ -17,7 +17,12 @@ export default meta type Story = StoryObj export const NonTabularFiles: Story = { - render: () => + render: () => ( + + ) } export const TabularFiles: Story = { @@ -30,6 +35,7 @@ export const TabularFiles: Story = { unf: 'some-unf' } })} + fileSelection={{}} /> ) } diff --git a/src/stories/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.stories.tsx b/src/stories/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.stories.tsx index a50a9cff0..692bc1980 100644 --- a/src/stories/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.stories.tsx +++ b/src/stories/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.stories.tsx @@ -16,5 +16,5 @@ export default meta type Story = StoryObj export const Default: Story = { - render: () => + render: () => } diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/FileActionsHeader.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/FileActionsHeader.spec.tsx index 3b8cbc009..1baf2e865 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/FileActionsHeader.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/FileActionsHeader.spec.tsx @@ -20,7 +20,7 @@ describe('FileActionsHeader', () => { - + ) diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx index c3d8ab9fe..e380f0786 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx @@ -30,7 +30,10 @@ describe('DownloadFilesButton', () => { }) const files = FileMother.createMany(2) cy.mountAuthenticated( - withDataset(, datasetWithDownloadFilesPermission) + withDataset( + , + datasetWithDownloadFilesPermission + ) ) cy.findByRole('button', { name: 'Download' }).should('exist') @@ -42,7 +45,10 @@ describe('DownloadFilesButton', () => { }) const files = FileMother.createMany(1) cy.mountAuthenticated( - withDataset(, datasetWithDownloadFilesPermission) + withDataset( + , + datasetWithDownloadFilesPermission + ) ) cy.findByRole('button', { name: 'Download' }).should('not.exist') @@ -54,7 +60,10 @@ describe('DownloadFilesButton', () => { }) const files = FileMother.createMany(2) cy.mountAuthenticated( - withDataset(, datasetWithoutDownloadFilesPermission) + withDataset( + , + datasetWithoutDownloadFilesPermission + ) ) cy.findByRole('button', { name: 'Download' }).should('not.exist') @@ -72,7 +81,10 @@ describe('DownloadFilesButton', () => { } }) cy.mountAuthenticated( - withDataset(, datasetWithDownloadFilesPermission) + withDataset( + , + datasetWithDownloadFilesPermission + ) ) cy.findByRole('button', { name: 'Download' }).click() @@ -86,11 +98,49 @@ describe('DownloadFilesButton', () => { }) const files = FileMother.createMany(2, { tabularData: undefined }) cy.mountAuthenticated( - withDataset(, datasetWithDownloadFilesPermission) + withDataset( + , + datasetWithDownloadFilesPermission + ) ) cy.findByRole('button', { name: 'Download' }).click() cy.findByRole('button', { name: 'Original Format' }).should('not.exist') cy.findByRole('button', { name: 'Archival Format (.tab)' }).should('not.exist') }) + + it('shows the No Selected Files modal if no files are selected', () => { + const datasetWithDownloadFilesPermission = DatasetMother.create({ + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() + }) + const files = FileMother.createMany(2) + cy.mountAuthenticated( + withDataset( + , + datasetWithDownloadFilesPermission + ) + ) + + cy.findByRole('button', { name: 'Download' }).click() + cy.findByText('Select File(s)').should('exist') + }) + + it('does not show the No Selected Files modal if files are selected', () => { + const datasetWithDownloadFilesPermission = DatasetMother.create({ + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() + }) + const files = FileMother.createMany(2) + cy.mountAuthenticated( + withDataset( + , + datasetWithDownloadFilesPermission + ) + ) + + cy.findByRole('button', { name: 'Download' }).click() + cy.findByText('Select File(s)').should('not.exist') + }) }) diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.spec.tsx index eaa400bd2..0cc9ac5b9 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.spec.tsx @@ -32,27 +32,31 @@ describe('EditFilesMenu', () => { it('renders the Edit Files menu', () => { cy.mountAuthenticated( - withDataset(, datasetWithUpdatePermissions) + withDataset(, datasetWithUpdatePermissions) ) cy.findByRole('button', { name: 'Edit Files' }).should('exist') }) it('does not render the Edit Files menu when the user is not authenticated', () => { - cy.customMount(withDataset(, datasetWithUpdatePermissions)) + cy.customMount( + withDataset(, datasetWithUpdatePermissions) + ) cy.findByRole('button', { name: 'Edit Files' }).should('not.exist') }) it('does not render the Edit Files menu when there are no files in the dataset', () => { - cy.mountAuthenticated(withDataset(, datasetWithUpdatePermissions)) + cy.mountAuthenticated( + withDataset(, datasetWithUpdatePermissions) + ) cy.findByRole('button', { name: 'Edit Files' }).should('not.exist') }) it('renders the Edit Files options', () => { cy.mountAuthenticated( - withDataset(, datasetWithUpdatePermissions) + withDataset(, datasetWithUpdatePermissions) ) cy.findByRole('button', { name: 'Edit Files' }).click() @@ -65,7 +69,10 @@ describe('EditFilesMenu', () => { }) cy.mountAuthenticated( - withDataset(, datasetWithNoUpdatePermissions) + withDataset( + , + datasetWithNoUpdatePermissions + ) ) cy.findByRole('button', { name: 'Edit Files' }).should('not.exist') @@ -78,7 +85,7 @@ describe('EditFilesMenu', () => { }) cy.mountAuthenticated( - withDataset(, datasetWithUpdatePermissions) + withDataset(, datasetWithUpdatePermissions) ) cy.findByRole('button', { name: 'Edit Files' }).should('be.disabled') @@ -91,7 +98,7 @@ describe('EditFilesMenu', () => { }) cy.mountAuthenticated( - withDataset(, datasetWithUpdatePermissions) + withDataset(, datasetWithUpdatePermissions) ) cy.findByRole('button', { name: 'Edit Files' }).should('be.disabled') diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesOptions.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesOptions.spec.tsx index 8006fe0aa..2571bfcf9 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesOptions.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesOptions.spec.tsx @@ -4,7 +4,7 @@ import { FileMother } from '../../../../../../files/domain/models/FileMother' const files = FileMother.createMany(2) describe('EditFilesOptions', () => { it('renders the EditFilesOptions', () => { - cy.customMount() + cy.customMount() cy.findByRole('button', { name: 'Metadata' }).should('exist') cy.findByRole('button', { name: 'Replace' }).should('exist') @@ -15,27 +15,66 @@ describe('EditFilesOptions', () => { it('renders the restrict option if some file is unrestricted', () => { const fileUnrestricted = FileMother.createDefault() - cy.customMount() + cy.customMount() - cy.findByRole('button', { name: 'Restrict' }).should('exist') + cy.findByRole('button', { name: 'Restrict' }).should('exist').click() + cy.findByText('Select File(s)').should('exist') + cy.findByText('Close').click() }) it('renders the unrestrict option if some file is restricted', () => { const fileRestricted = FileMother.createWithRestrictedAccess() - cy.customMount() + cy.customMount() - cy.findByRole('button', { name: 'Unrestrict' }).should('exist') + cy.findByRole('button', { name: 'Unrestrict' }).should('exist').click() + cy.findByText('Select File(s)').should('exist') + cy.findByText('Close').click() }) it.skip('renders the embargo option if the embargo is allowed by settings', () => { - cy.customMount() + cy.customMount() - cy.findByRole('button', { name: 'Embargo' }).should('exist') + cy.findByRole('button', { name: 'Embargo' }).should('exist').click() + cy.findByText('Select File(s)').should('exist') + cy.findByText('Close').click() }) it.skip('renders provenance option if provenance is enabled in config', () => { - cy.customMount() + cy.customMount() - cy.findByRole('button', { name: 'Provenance' }).should('exist') + cy.findByRole('button', { name: 'Provenance' }).should('exist').click() + cy.findByText('Select File(s)').should('exist') + cy.findByText('Close').click() + }) + + it('shows the No Selected Files message when no files are selected and one option is clicked', () => { + cy.customMount() + + cy.findByRole('button', { name: 'Metadata' }).click() + cy.findByText('Select File(s)').should('exist') + cy.findByText('Close').click() + + cy.findByRole('button', { name: 'Replace' }).click() + cy.findByText('Select File(s)').should('exist') + cy.findByText('Close').click() + + cy.findByRole('button', { name: 'Delete' }).click() + cy.findByText('Select File(s)').should('exist') + cy.findByText('Close').click() + }) + + it('does not show the No Selected Files message when files are selected and one option is clicked', () => { + cy.customMount( + + ) + + cy.findByRole('button', { name: 'Metadata' }).click() + cy.findByText('Select File(s)').should('not.exist') + + cy.findByRole('button', { name: 'Replace' }).click() + cy.findByText('Select File(s)').should('not.exist') + + cy.findByRole('button', { name: 'Delete' }).click() + cy.findByText('Select File(s)').should('not.exist') }) })