diff --git a/src/dataset/domain/models/Dataset.ts b/src/dataset/domain/models/Dataset.ts index f500394ae..345bbfd79 100644 --- a/src/dataset/domain/models/Dataset.ts +++ b/src/dataset/domain/models/Dataset.ts @@ -264,6 +264,11 @@ export interface PrivateUrl { urlSnippet: string } +export interface DatasetDownloadUrls { + original: string + archival: string +} + export class Dataset { constructor( public readonly persistentId: string, @@ -280,6 +285,7 @@ export class Dataset { public readonly hasOneTabularFileAtLeast: boolean, public readonly isValid: boolean, public readonly isReleased: boolean, + public readonly downloadUrls: DatasetDownloadUrls, public readonly thumbnail?: string, public readonly privateUrl?: PrivateUrl, public readonly fileDownloadSizes?: FileDownloadSize[] @@ -365,6 +371,7 @@ export class Dataset { public readonly hasOneTabularFileAtLeast: boolean, public readonly isValid: boolean, public readonly isReleased: boolean, + public readonly downloadUrls: DatasetDownloadUrls, public readonly thumbnail?: string, public readonly privateUrl?: PrivateUrl, public readonly fileDownloadSizes?: FileDownloadSize[] @@ -475,6 +482,7 @@ export class Dataset { this.hasOneTabularFileAtLeast, this.isValid, this.isReleased, + this.downloadUrls, this.thumbnail, this.privateUrl, this.fileDownloadSizes diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index fe5bb7fc0..95703678d 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -16,10 +16,11 @@ import { DatasetMetadataFields, DatasetVersion, MetadataBlockName, + PrivateUrl, + DatasetDownloadUrls, DatasetPermissions, DatasetLock, - DatasetLockReason, - PrivateUrl + DatasetLockReason } from '../../domain/models/Dataset' export class JSDatasetMapper { @@ -32,9 +33,14 @@ export class JSDatasetMapper { requestedVersion?: string, privateUrl?: PrivateUrl ): Dataset { + const version = JSDatasetMapper.toVersion( + jsDataset.versionId, + jsDataset.versionInfo, + requestedVersion + ) return new Dataset.Builder( jsDataset.persistentId, - JSDatasetMapper.toVersion(jsDataset.versionId, jsDataset.versionInfo, requestedVersion), + version, citation, JSDatasetMapper.toSummaryFields(jsDataset.metadataBlocks, summaryFieldsNames), jsDataset.license, @@ -50,6 +56,7 @@ export class JSDatasetMapper { true, // TODO Connect with dataset hasOneTabularFileAtLeast true, // TODO Connect with dataset isValid JSDatasetMapper.toIsReleased(jsDataset.versionInfo), + JSDatasetMapper.toDownloadUrls(jsDataset.persistentId, version), undefined, // TODO: get dataset thumbnail from Dataverse https://github.com/IQSS/dataverse-frontend/issues/203 privateUrl, [] // TODO: Connect with file download use case @@ -187,6 +194,15 @@ export class JSDatasetMapper { return extraFields } + static toDownloadUrls( + jsDatasetPersistentId: string, + version: DatasetVersion + ): DatasetDownloadUrls { + return { + original: `/api/access/dataset/:persistentId/versions/${version.toString()}?persistentId=${jsDatasetPersistentId}&format=original`, + archival: `/api/access/dataset/:persistentId/versions/${version.toString()}?persistentId=${jsDatasetPersistentId}` + } + } static toIsReleased(jsDatasetVersionInfo: JSDatasetVersionInfo): boolean { return ( jsDatasetVersionInfo.releaseTime !== undefined && diff --git a/src/files/domain/models/File.ts b/src/files/domain/models/File.ts index 7b78f9a47..ad09ce9f1 100644 --- a/src/files/domain/models/File.ts +++ b/src/files/domain/models/File.ts @@ -63,17 +63,11 @@ export class FileSize { } } -export enum FileDownloadSizeMode { - ALL = 'All', - ORIGINAL = 'Original', - ARCHIVAL = 'Archival' -} - export class FileDownloadSize extends FileSize { constructor( readonly value: number, readonly unit: FileSizeUnit, - readonly mode: FileDownloadSizeMode + readonly mode: FileDownloadMode ) { super(value, unit) } @@ -127,6 +121,11 @@ export interface FileTabularData { unf?: string } +export enum FileDownloadMode { + ARCHIVAL = 'archival', + ORIGINAL = 'original' +} + export enum FileLabelType { CATEGORY = 'category', TAG = 'tag' diff --git a/src/files/domain/repositories/FileRepository.ts b/src/files/domain/repositories/FileRepository.ts index 976fd5a66..9b861084d 100644 --- a/src/files/domain/repositories/FileRepository.ts +++ b/src/files/domain/repositories/FileRepository.ts @@ -1,4 +1,4 @@ -import { File } from '../models/File' +import { File, FileDownloadMode } from '../models/File' import { FileCriteria } from '../models/FileCriteria' import { FilesCountInfo } from '../models/FilesCountInfo' import { FilePaginationInfo } from '../models/FilePaginationInfo' @@ -23,4 +23,6 @@ export interface FileRepository { criteria?: FileCriteria ) => Promise getUserPermissionsById: (id: number) => Promise + getMultipleFileDownloadUrl: (ids: number[], downloadMode: FileDownloadMode) => string + getFileDownloadUrl: (id: number, downloadMode: FileDownloadMode) => string } diff --git a/src/files/domain/useCases/getMultipleFileDownloadUrl.ts b/src/files/domain/useCases/getMultipleFileDownloadUrl.ts new file mode 100644 index 000000000..fac6afcf3 --- /dev/null +++ b/src/files/domain/useCases/getMultipleFileDownloadUrl.ts @@ -0,0 +1,15 @@ +import { FileRepository } from '../repositories/FileRepository' +import { FileDownloadMode } from '../models/File' + +const ONLY_ONE_FILE = 1 +export function getMultipleFileDownloadUrl( + fileRepository: FileRepository, + ids: number[], + downloadMode: FileDownloadMode +): string { + if (ids.length === ONLY_ONE_FILE) { + return fileRepository.getFileDownloadUrl(ids[0], downloadMode) + } + + return fileRepository.getMultipleFileDownloadUrl(ids, downloadMode) +} diff --git a/src/files/infrastructure/FileJSDataverseRepository.ts b/src/files/infrastructure/FileJSDataverseRepository.ts index e96c994bf..96affa553 100644 --- a/src/files/infrastructure/FileJSDataverseRepository.ts +++ b/src/files/infrastructure/FileJSDataverseRepository.ts @@ -1,19 +1,19 @@ import { FileRepository } from '../domain/repositories/FileRepository' -import { File } from '../domain/models/File' +import { File, FileDownloadMode } from '../domain/models/File' import { FilesCountInfo } from '../domain/models/FilesCountInfo' import { FilePaginationInfo } from '../domain/models/FilePaginationInfo' import { FileUserPermissions } from '../domain/models/FileUserPermissions' import { + File as JSFile, + FileDataTable as JSFileTabularData, FileDownloadSizeMode, getDatasetFileCounts, getDatasetFiles, getDatasetFilesTotalDownloadSize, + getFileDataTables, getFileDownloadCount, getFileUserPermissions, - ReadError, - File as JSFile, - getFileDataTables, - FileDataTable as JSFileTabularData + ReadError } from '@iqss/dataverse-client-javascript' import { FileCriteria } from '../domain/models/FileCriteria' import { DomainFileMapper } from './mappers/DomainFileMapper' @@ -155,4 +155,15 @@ export class FileJSDataverseRepository implements FileRepository { throw new Error(error.message) }) } + + getMultipleFileDownloadUrl(ids: number[], downloadMode: FileDownloadMode): string { + return `/api/access/datafiles/${ids.join(',')}?format=${downloadMode}` + } + + getFileDownloadUrl(id: number, downloadMode: FileDownloadMode): string { + if (downloadMode === FileDownloadMode.ORIGINAL) { + return `/api/access/datafile/${id}?format=${downloadMode}` + } + return `/api/access/datafile/${id}` + } } diff --git a/src/sections/dataset/DatasetFactory.tsx b/src/sections/dataset/DatasetFactory.tsx index 7cbdecc0b..cb2909c0f 100644 --- a/src/sections/dataset/DatasetFactory.tsx +++ b/src/sections/dataset/DatasetFactory.tsx @@ -11,6 +11,7 @@ import { SettingJSDataverseRepository } from '../../settings/infrastructure/Sett import { FilePermissionsProvider } from '../file/file-permissions/FilePermissionsProvider' import { SettingsProvider } from '../settings/SettingsProvider' import { DatasetProvider } from './DatasetProvider' +import { MultipleFileDownloadProvider } from '../file/multiple-file-download/MultipleFileDownloadProvider' import { NotImplementedModalProvider } from '../not-implemented/NotImplementedModalProvider' import { AlertProvider } from '../alerts/AlertProvider' @@ -22,19 +23,21 @@ const settingRepository = new SettingJSDataverseRepository() export class DatasetFactory { static create(): ReactElement { return ( - - - - - - - - - - - - - + + + + + + + + + + + + + + + ) } } diff --git a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx index 23a52d5a5..1b3591596 100644 --- a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx @@ -6,7 +6,7 @@ import { import { DropdownButton, DropdownButtonItem, DropdownHeader } from '@iqss/dataverse-design-system' import { useTranslation } from 'react-i18next' -import { FileDownloadSize, FileDownloadSizeMode } from '../../../../files/domain/models/File' +import { FileDownloadMode, FileDownloadSize } from '../../../../files/domain/models/File' import { Download } from 'react-bootstrap-icons' interface AccessDatasetMenuProps { @@ -30,12 +30,12 @@ export function AccessDatasetMenu({ return <> } - function getFormattedFileSize(mode: FileDownloadSizeMode): string { + function getFormattedFileSize(mode: FileDownloadMode): string { const foundSize = fileDownloadSizes && fileDownloadSizes.find((size) => size.mode === mode) return foundSize ? foundSize.toString() : '' } - const handleDownload = (type: FileDownloadSizeMode) => { + const handleDownload = (type: FileDownloadMode) => { //TODO: implement download feature console.log('downloading file ' + type) } @@ -47,19 +47,19 @@ export function AccessDatasetMenu({ const DatasetDownloadOptions = ({ datasetContainsTabularFiles }: DatasetDownloadOptionsProps) => { return datasetContainsTabularFiles ? ( <> - handleDownload(FileDownloadSizeMode.ORIGINAL)}> + handleDownload(FileDownloadMode.ORIGINAL)}> {t('datasetActionButtons.accessDataset.downloadOriginalZip')} ( - {getFormattedFileSize(FileDownloadSizeMode.ORIGINAL)}) + {getFormattedFileSize(FileDownloadMode.ORIGINAL)}) - handleDownload(FileDownloadSizeMode.ARCHIVAL)}> + handleDownload(FileDownloadMode.ARCHIVAL)}> {t('datasetActionButtons.accessDataset.downloadArchiveZip')} ( - {getFormattedFileSize(FileDownloadSizeMode.ARCHIVAL)}) + {getFormattedFileSize(FileDownloadMode.ARCHIVAL)}) ) : ( - handleDownload(FileDownloadSizeMode.ORIGINAL)}> + handleDownload(FileDownloadMode.ORIGINAL)}> {t('datasetActionButtons.accessDataset.downloadZip')} ( - {getFormattedFileSize(FileDownloadSizeMode.ORIGINAL)}) + {getFormattedFileSize(FileDownloadMode.ORIGINAL)}) ) } 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 887b4c940..67e1596ff 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 @@ -1,4 +1,4 @@ -import { File } from '../../../../../../files/domain/models/File' +import { File, FileDownloadMode } from '../../../../../../files/domain/models/File' import { useDataset } from '../../../../DatasetContext' import { Button, DropdownButton, DropdownButtonItem } from '@iqss/dataverse-design-system' import { Download } from 'react-bootstrap-icons' @@ -6,8 +6,8 @@ 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' -import { useNotImplementedModal } from '../../../../../not-implemented/NotImplementedModalContext' +import { MouseEvent, useState } from 'react' +import { useMultipleFileDownload } from '../../../../../file/multiple-file-download/MultipleFileDownloadContext' interface DownloadFilesButtonProps { files: File[] @@ -21,11 +21,23 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto const { t } = useTranslation('files') const { dataset } = useDataset() const [showNoFilesSelectedModal, setShowNoFilesSelectedModal] = useState(false) - const handleClick = () => { - // TODO - Implement upload files - showModal() + const { getMultipleFileDownloadUrl } = useMultipleFileDownload() + const fileSelectionCount = Object.keys(fileSelection).length + const onClick = (event: MouseEvent) => { + if (fileSelectionCount === SELECTED_FILES_EMPTY) { + event.preventDefault() + setShowNoFilesSelectedModal(true) + } } - const { showModal } = useNotImplementedModal() + const getDownloadUrl = (downloadMode: FileDownloadMode): string => { + const allFilesSelected = Object.values(fileSelection).some((file) => file === undefined) + if (allFilesSelected) { + return dataset ? dataset.downloadUrls[downloadMode] : '' + } + + return getMultipleFileDownloadUrl(getFileIdsFromSelection(fileSelection), downloadMode) + } + if ( files.length < MINIMUM_FILES_COUNT_TO_SHOW_DOWNLOAD_FILES_BUTTON || !dataset?.permissions.canDownloadFiles @@ -33,15 +45,7 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto return <> } - const onClick = () => { - if (Object.keys(fileSelection).length === SELECTED_FILES_EMPTY) { - setShowNoFilesSelectedModal(true) - } else { - handleClick() - } - } - - if (files.some((file) => file.isTabularData)) { + if (dataset.hasOneTabularFileAtLeast) { return ( <> - + {t('actions.downloadFiles.options.original')} - + {t('actions.downloadFiles.options.archival')} @@ -67,13 +71,15 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto return ( <> - + + + setShowNoFilesSelectedModal(false)} @@ -81,3 +87,9 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto ) } + +const getFileIdsFromSelection = (fileSelection: FileSelection): number[] => { + return Object.values(fileSelection) + .filter((file): file is File => file !== undefined) + .map((file) => file.id) +} diff --git a/src/sections/file/multiple-file-download/MultipleFileDownloadContext.ts b/src/sections/file/multiple-file-download/MultipleFileDownloadContext.ts new file mode 100644 index 000000000..95c611513 --- /dev/null +++ b/src/sections/file/multiple-file-download/MultipleFileDownloadContext.ts @@ -0,0 +1,15 @@ +import { createContext, useContext } from 'react' +import { FileDownloadMode } from '../../../files/domain/models/File' + +interface MultipleFileDownloadContextProps { + getMultipleFileDownloadUrl: (ids: number[], downloadMode: FileDownloadMode) => string +} + +export const MultipleFileDownloadContext = createContext({ + getMultipleFileDownloadUrl: () => { + console.error('Not implemented') + return '' + } +}) + +export const useMultipleFileDownload = () => useContext(MultipleFileDownloadContext) diff --git a/src/sections/file/multiple-file-download/MultipleFileDownloadProvider.tsx b/src/sections/file/multiple-file-download/MultipleFileDownloadProvider.tsx new file mode 100644 index 000000000..39c485b94 --- /dev/null +++ b/src/sections/file/multiple-file-download/MultipleFileDownloadProvider.tsx @@ -0,0 +1,24 @@ +import { PropsWithChildren } from 'react' +import { FileRepository } from '../../../files/domain/repositories/FileRepository' +import { MultipleFileDownloadContext } from './MultipleFileDownloadContext' +import { FileDownloadMode } from '../../../files/domain/models/File' +import { getMultipleFileDownloadUrl } from '../../../files/domain/useCases/getMultipleFileDownloadUrl' + +interface MultipleFileDownloadProviderProps { + repository: FileRepository +} + +export function MultipleFileDownloadProvider({ + repository, + children +}: PropsWithChildren) { + const getDownloadUrl = (ids: number[], downloadMode: FileDownloadMode) => { + return getMultipleFileDownloadUrl(repository, ids, downloadMode) + } + + return ( + + {children} + + ) +} diff --git a/src/settings/infrastructure/SettingJSDataverseRepository.ts b/src/settings/infrastructure/SettingJSDataverseRepository.ts index e55e995fe..daeb13e14 100644 --- a/src/settings/infrastructure/SettingJSDataverseRepository.ts +++ b/src/settings/infrastructure/SettingJSDataverseRepository.ts @@ -20,7 +20,7 @@ function mockedSettingResponse(name: SettingName): Setting { case SettingName.ZIP_DOWNLOAD_LIMIT: return { name: SettingName.ZIP_DOWNLOAD_LIMIT, - value: new ZipDownloadLimit(1, FileSizeUnit.BYTES) + value: new ZipDownloadLimit(1, FileSizeUnit.TERABYTES) } as Setting case SettingName.ALLOWED_EXTERNAL_STATUSES: return { diff --git a/src/stories/files/FileMockLoadingRepository.ts b/src/stories/files/FileMockLoadingRepository.ts index eff4bd897..949ad992c 100644 --- a/src/stories/files/FileMockLoadingRepository.ts +++ b/src/stories/files/FileMockLoadingRepository.ts @@ -1,12 +1,11 @@ import { FileRepository } from '../../files/domain/repositories/FileRepository' import { File } from '../../files/domain/models/File' import { FilesCountInfo } from '../../files/domain/models/FilesCountInfo' -import { FileUserPermissions } from '../../files/domain/models/FileUserPermissions' -import { FileUserPermissionsMother } from '../../../tests/component/files/domain/models/FileUserPermissionsMother' import { DatasetVersion } from '../../dataset/domain/models/Dataset' import { FileCriteria } from '../../files/domain/models/FileCriteria' +import { FileMockRepository } from './FileMockRepository' -export class FileMockLoadingRepository implements FileRepository { +export class FileMockLoadingRepository extends FileMockRepository implements FileRepository { getAllByDatasetPersistentId( // eslint-disable-next-line unused-imports/no-unused-vars datasetPersistentId: string, @@ -26,7 +25,7 @@ export class FileMockLoadingRepository implements FileRepository { // eslint-disable-next-line unused-imports/no-unused-vars datasetVersion: DatasetVersion, // eslint-disable-next-line unused-imports/no-unused-vars - criteria: FileCriteria + criteria?: FileCriteria ): Promise { return new Promise(() => { setTimeout(() => { @@ -34,27 +33,4 @@ export class FileMockLoadingRepository implements FileRepository { }, 1000) }) } - // eslint-disable-next-line unused-imports/no-unused-vars - getUserPermissionsById(id: number): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(FileUserPermissionsMother.create()) - }, 1000) - }) - } - - getFilesTotalDownloadSizeByDatasetPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - datasetPersistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - datasetVersion: DatasetVersion, - // eslint-disable-next-line unused-imports/no-unused-vars - criteria?: FileCriteria - ): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(19900) - }, 1000) - }) - } } diff --git a/src/stories/files/FileMockNoDataRepository.ts b/src/stories/files/FileMockNoDataRepository.ts index ba7c10a3b..51813c757 100644 --- a/src/stories/files/FileMockNoDataRepository.ts +++ b/src/stories/files/FileMockNoDataRepository.ts @@ -2,12 +2,11 @@ import { FileRepository } from '../../files/domain/repositories/FileRepository' import { File } from '../../files/domain/models/File' import { FilesCountInfo } from '../../files/domain/models/FilesCountInfo' import { FilesCountInfoMother } from '../../../tests/component/files/domain/models/FilesCountInfoMother' -import { FileUserPermissions } from '../../files/domain/models/FileUserPermissions' -import { FileUserPermissionsMother } from '../../../tests/component/files/domain/models/FileUserPermissionsMother' import { DatasetVersion } from '../../dataset/domain/models/Dataset' import { FileCriteria } from '../../files/domain/models/FileCriteria' +import { FileMockRepository } from './FileMockRepository' -export class FileMockNoDataRepository implements FileRepository { +export class FileMockNoDataRepository extends FileMockRepository implements FileRepository { getAllByDatasetPersistentId( // eslint-disable-next-line unused-imports/no-unused-vars datasetPersistentId: string, @@ -48,13 +47,4 @@ export class FileMockNoDataRepository implements FileRepository { }, 1000) }) } - - // eslint-disable-next-line unused-imports/no-unused-vars - getUserPermissionsById(id: number): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(FileUserPermissionsMother.create()) - }, 1000) - }) - } } diff --git a/src/stories/files/FileMockNoFiltersRepository.ts b/src/stories/files/FileMockNoFiltersRepository.ts index eec689eaa..2ce08e666 100644 --- a/src/stories/files/FileMockNoFiltersRepository.ts +++ b/src/stories/files/FileMockNoFiltersRepository.ts @@ -3,12 +3,11 @@ import { File } from '../../files/domain/models/File' import { FilesCountInfo } from '../../files/domain/models/FilesCountInfo' import { FilesCountInfoMother } from '../../../tests/component/files/domain/models/FilesCountInfoMother' import { FilesMockData } from './FileMockData' -import { FileUserPermissions } from '../../files/domain/models/FileUserPermissions' -import { FileUserPermissionsMother } from '../../../tests/component/files/domain/models/FileUserPermissionsMother' import { DatasetVersion } from '../../dataset/domain/models/Dataset' import { FileCriteria } from '../../files/domain/models/FileCriteria' +import { FileMockRepository } from './FileMockRepository' -export class FileMockNoFiltersRepository implements FileRepository { +export class FileMockNoFiltersRepository extends FileMockRepository implements FileRepository { getAllByDatasetPersistentId( // eslint-disable-next-line unused-imports/no-unused-vars datasetPersistentId: string, @@ -28,7 +27,7 @@ export class FileMockNoFiltersRepository implements FileRepository { // eslint-disable-next-line unused-imports/no-unused-vars datasetVersion: DatasetVersion, // eslint-disable-next-line unused-imports/no-unused-vars - criteria: FileCriteria + criteria?: FileCriteria ): Promise { return new Promise((resolve) => { setTimeout(() => { @@ -36,27 +35,4 @@ export class FileMockNoFiltersRepository implements FileRepository { }, 1000) }) } - // eslint-disable-next-line unused-imports/no-unused-vars - getUserPermissionsById(id: number): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(FileUserPermissionsMother.create()) - }, 1000) - }) - } - - getFilesTotalDownloadSizeByDatasetPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - datasetPersistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - datasetVersion: DatasetVersion, - // eslint-disable-next-line unused-imports/no-unused-vars - criteria?: FileCriteria - ): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(19900) - }, 1000) - }) - } } diff --git a/src/stories/files/FileMockRepository.ts b/src/stories/files/FileMockRepository.ts index 63e1499fa..317facbc4 100644 --- a/src/stories/files/FileMockRepository.ts +++ b/src/stories/files/FileMockRepository.ts @@ -1,6 +1,6 @@ import { FileRepository } from '../../files/domain/repositories/FileRepository' import { FilesMockData } from './FileMockData' -import { File } from '../../files/domain/models/File' +import { File, FileDownloadMode } from '../../files/domain/models/File' import { FilesCountInfo } from '../../files/domain/models/FilesCountInfo' import { FilesCountInfoMother } from '../../../tests/component/files/domain/models/FilesCountInfoMother' import { FilePaginationInfo } from '../../files/domain/models/FilePaginationInfo' @@ -8,6 +8,7 @@ import { FileUserPermissionsMother } from '../../../tests/component/files/domain import { FileUserPermissions } from '../../files/domain/models/FileUserPermissions' import { DatasetVersion } from '../../dataset/domain/models/Dataset' import { FileCriteria } from '../../files/domain/models/FileCriteria' +import { FileMother } from '../../../tests/component/files/domain/models/FileMother' export class FileMockRepository implements FileRepository { // eslint-disable-next-line unused-imports/no-unused-vars @@ -59,4 +60,14 @@ export class FileMockRepository implements FileRepository { }, 1000) }) } + + // eslint-disable-next-line unused-imports/no-unused-vars + getMultipleFileDownloadUrl(ids: number[], downloadMode: FileDownloadMode): string { + return FileMother.createDownloadUrl() + } + + // eslint-disable-next-line unused-imports/no-unused-vars + getFileDownloadUrl(id: number, downloadMode: FileDownloadMode): string { + return FileMother.createDownloadUrl() + } } diff --git a/tests/component/dataset/domain/models/DatasetMother.ts b/tests/component/dataset/domain/models/DatasetMother.ts index 460ecfb53..73d7b18f7 100644 --- a/tests/component/dataset/domain/models/DatasetMother.ts +++ b/tests/component/dataset/domain/models/DatasetMother.ts @@ -13,8 +13,8 @@ import { MetadataBlockName } from '../../../../../src/dataset/domain/models/Dataset' import { + FileDownloadMode, FileDownloadSize, - FileDownloadSizeMode, FileSizeUnit } from '../../../../../src/files/domain/models/File' @@ -199,16 +199,16 @@ export class DatasetFileDownloadSizeMother { return new FileDownloadSize( props?.value ?? faker.datatype.number(), props?.unit ?? faker.helpers.arrayElement(Object.values(FileSizeUnit)), - props?.mode ?? faker.helpers.arrayElement(Object.values(FileDownloadSizeMode)) + props?.mode ?? faker.helpers.arrayElement(Object.values(FileDownloadMode)) ) } static createArchival(): FileDownloadSize { - return this.create({ mode: FileDownloadSizeMode.ARCHIVAL }) + return this.create({ mode: FileDownloadMode.ARCHIVAL }) } static createOriginal(): FileDownloadSize { - return this.create({ mode: FileDownloadSizeMode.ORIGINAL }) + return this.create({ mode: FileDownloadMode.ORIGINAL }) } } @@ -315,6 +315,10 @@ export class DatasetMother { hasOneTabularFileAtLeast: faker.datatype.boolean(), isValid: faker.datatype.boolean(), isReleased: faker.datatype.boolean(), + downloadUrls: { + original: this.createDownloadUrl(), + archival: this.createDownloadUrl() + }, thumbnail: undefined, privateUrl: undefined, fileDownloadSizes: undefined, @@ -334,12 +338,20 @@ export class DatasetMother { dataset.hasOneTabularFileAtLeast, dataset.isValid, dataset.isReleased, + dataset.downloadUrls, dataset.thumbnail, dataset.privateUrl, dataset.fileDownloadSizes ).build() } + static createDownloadUrl(): string { + const blob = new Blob(['Name,Age,Location\nJohn,25,New York\nJane,30,San Francisco'], { + type: 'text/csv' + }) + return URL.createObjectURL(blob) + } + static createAnonymized(): Dataset { return this.create({ citation: @@ -470,8 +482,8 @@ export class DatasetMother { hasValidTermsOfAccess: true, hasOneTabularFileAtLeast: true, fileDownloadSizes: [ - new FileDownloadSize(21.98, FileSizeUnit.KILOBYTES, FileDownloadSizeMode.ORIGINAL), - new FileDownloadSize(21.98, FileSizeUnit.KILOBYTES, FileDownloadSizeMode.ARCHIVAL) + new FileDownloadSize(21.98, FileSizeUnit.KILOBYTES, FileDownloadMode.ORIGINAL), + new FileDownloadSize(21.98, FileSizeUnit.KILOBYTES, FileDownloadMode.ARCHIVAL) ], isValid: true, ...props diff --git a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts index 665145c6b..8af60a399 100644 --- a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts +++ b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts @@ -147,6 +147,10 @@ const expectedDataset = { isReleased: false, thumbnail: undefined, privateUrl: undefined, + downloadUrls: { + original: `/api/access/dataset/:persistentId/versions/0.0?persistentId=doi:10.5072/FK2/B4B2MJ&format=original`, + archival: `/api/access/dataset/:persistentId/versions/0.0?persistentId=doi:10.5072/FK2/B4B2MJ` + }, fileDownloadSizes: [] } const expectedDatasetAlternateVersion = { @@ -237,7 +241,11 @@ const expectedDatasetAlternateVersion = { canPublishDataset: true, canUpdateDataset: true }, - thumbnail: undefined + thumbnail: undefined, + downloadUrls: { + original: `/api/access/dataset/:persistentId/versions/0.0?persistentId=doi:10.5072/FK2/B4B2MJ&format=original`, + archival: `/api/access/dataset/:persistentId/versions/0.0?persistentId=doi:10.5072/FK2/B4B2MJ` + } } describe('JS Dataset Mapper', () => { it('maps jsDataset model to the domain Dataset model', () => { diff --git a/tests/component/files/domain/models/FileMother.ts b/tests/component/files/domain/models/FileMother.ts index 880cbb459..130ff8a62 100644 --- a/tests/component/files/domain/models/FileMother.ts +++ b/tests/component/files/domain/models/FileMother.ts @@ -29,11 +29,6 @@ export class FileEmbargoMother { static create(dateAvailable?: Date): FileEmbargo { return new FileEmbargo(dateAvailable ?? faker.date.future()) } - - static createNotActive(): FileEmbargo { - const dateAvailable = faker.date.past() - return new FileEmbargo(dateAvailable) - } } export class FileIngestMother { static create(props?: Partial): FileIngest { diff --git a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx index f085f51d2..9a3ff1e0d 100644 --- a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx +++ b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx @@ -48,10 +48,10 @@ describe('AccessDatasetMenu', () => { cy.findByRole('button', { name: 'Access Dataset' }).should('exist') cy.findByRole('button', { name: 'Access Dataset' }).click() cy.contains('Original Format ZIP').click() - cy.get('@consoleLog').should('have.been.calledWith', 'downloading file Original') + cy.get('@consoleLog').should('have.been.calledWith', 'downloading file original') cy.findByRole('button', { name: 'Access Dataset' }).click() cy.contains('Archive Format').click() - cy.get('@consoleLog').should('have.been.calledWith', 'downloading file Archival') + cy.get('@consoleLog').should('have.been.calledWith', 'downloading file archival') }) it('renders the AccessDatasetMenu if the user has download files permissions and the dataset is not deaccessioned', () => { 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 a82b1ea15..b1b197d1b 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 @@ -8,8 +8,11 @@ import { } from '../../../../../../dataset/domain/models/DatasetMother' import { DownloadFilesButton } from '../../../../../../../../src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton' import { FileMother } from '../../../../../../files/domain/models/FileMother' +import { MultipleFileDownloadProvider } from '../../../../../../../../src/sections/file/multiple-file-download/MultipleFileDownloadProvider' +import { FileRepository } from '../../../../../../../../src/files/domain/repositories/FileRepository' const datasetRepository: DatasetRepository = {} as DatasetRepository +const fileRepository = {} as FileRepository describe('DownloadFilesButton', () => { const withDataset = (component: ReactNode, dataset: DatasetModel | undefined) => { datasetRepository.getByPersistentId = cy.stub().resolves(dataset) @@ -24,6 +27,12 @@ describe('DownloadFilesButton', () => { ) } + beforeEach(() => { + fileRepository.getMultipleFileDownloadUrl = cy + .stub() + .returns('https://multiple-file-download-url') + }) + it('renders the Download Files button if there is more than 1 file in the dataset and the user has download files permission', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() @@ -71,7 +80,8 @@ describe('DownloadFilesButton', () => { it('renders the Download Files button as a dropdown if there are tabular files in the dataset', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ - permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: true }) const files = FileMother.createMany(2, { tabularData: { @@ -94,7 +104,8 @@ describe('DownloadFilesButton', () => { it('does not render the Download Files button as a dropdown if there are no tabular files in the dataset', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ - permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: false }) const files = FileMother.createMany(2, { tabularData: undefined }) cy.mountAuthenticated( @@ -111,7 +122,8 @@ describe('DownloadFilesButton', () => { it('shows the No Selected Files modal if no files are selected', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ - permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: false }) const files = FileMother.createMany(2, { tabularData: undefined }) cy.mountAuthenticated( @@ -125,22 +137,143 @@ describe('DownloadFilesButton', () => { cy.findByText('Select File(s)').should('exist') }) - it('does not show the No Selected Files modal if files are selected', () => { + it('renders the download url for the selected files when some files are selected and there are no tabular files', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ - permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: false }) - const files = FileMother.createMany(2) + const files = FileMother.createMany(2, { tabularData: undefined }) + const fileSelection = { + 'some-file-id': files[0], + 'some-other-file-id': files[1] + } + cy.mountAuthenticated( + + {withDataset( + , + datasetWithDownloadFilesPermission + )} + + ) + + cy.findByRole('button', { name: 'Download' }) + .parent('a') + .should('have.attr', 'href', 'https://multiple-file-download-url') + }) + + it('renders the download url for the selected files when some files are selected and there are tabular files', () => { + const datasetWithDownloadFilesPermission = DatasetMother.create({ + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: true + }) + const files = FileMother.createMany(2, { + tabularData: { + variablesCount: 2, + observationsCount: 3, + unf: 'some-unf' + } + }) + const fileSelection = { + 'some-file-id': files[0], + 'some-other-file-id': files[1] + } + cy.mountAuthenticated( + + {withDataset( + , + datasetWithDownloadFilesPermission + )} + + ) + + cy.findByRole('button', { name: 'Download' }).click() + cy.findByRole('link', { name: 'Original Format' }).should( + 'have.attr', + 'href', + 'https://multiple-file-download-url' + ) + cy.findByRole('link', { name: 'Archival Format (.tab)' }).should( + 'have.attr', + 'href', + 'https://multiple-file-download-url' + ) + }) + + it('renders the dataset download url when all the files are selected', () => { + const datasetWithDownloadFilesPermission = DatasetMother.create({ + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: true, + downloadUrls: { + original: 'https://dataset-download-url-original', + archival: 'https://dataset-download-url-archival' + } + }) + const files = FileMother.createMany(2, { + tabularData: { + variablesCount: 2, + observationsCount: 3, + unf: 'some-unf' + } + }) + const fileSelection = { + 'some-file-id': undefined, + 'some-other-file-id': undefined + } cy.mountAuthenticated( withDataset( - , + , datasetWithDownloadFilesPermission ) ) cy.findByRole('button', { name: 'Download' }).click() - cy.findByText('Select File(s)').should('not.exist') + cy.findByRole('link', { name: 'Original Format' }).should( + 'have.attr', + 'href', + 'https://dataset-download-url-original' + ) + cy.findByRole('link', { name: 'Archival Format (.tab)' }).should( + 'have.attr', + 'href', + 'https://dataset-download-url-archival' + ) + }) + + it('renders the dataset download url with the single file download url when one file is selected', () => { + const datasetWithDownloadFilesPermission = DatasetMother.create({ + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: true + }) + const files = FileMother.createMany(2, { + tabularData: { + variablesCount: 2, + observationsCount: 3, + unf: 'some-unf' + } + }) + const fileSelection = { + 'some-file-id': files[0] + } + fileRepository.getFileDownloadUrl = cy.stub().returns('https://single-file-download-url') + cy.mountAuthenticated( + + {withDataset( + , + datasetWithDownloadFilesPermission + )} + + ) + + cy.findByRole('button', { name: 'Download' }).click() + cy.findByRole('link', { name: 'Original Format' }).should( + 'have.attr', + 'href', + 'https://single-file-download-url' + ) + cy.findByRole('link', { name: 'Archival Format (.tab)' }).should( + 'have.attr', + 'href', + 'https://single-file-download-url' + ) }) }) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 9022be854..c7bc50488 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -372,7 +372,7 @@ describe('Dataset', () => { }) }) - it.only('applies filters to the Files Table in the correct order', () => { + it('applies filters to the Files Table in the correct order', () => { const files = [ FileHelper.create('csv', { description: 'Some description', @@ -510,35 +510,71 @@ describe('Dataset', () => { }) }) - it('downloads a file', () => { - cy.wrap( - DatasetHelper.createWithFiles(FileHelper.createMany(1)).then((dataset) => - DatasetHelper.publish(dataset.persistentId) + describe('Downloading files', () => { + it('downloads a file', () => { + cy.wrap( + DatasetHelper.createWithFiles(FileHelper.createMany(1)).then((dataset) => + DatasetHelper.publish(dataset.persistentId) + ) ) - ) - .its('persistentId') - .then((persistentId: string) => { - cy.wait(1500) // Wait for the dataset to be published - cy.visit(`/spa/datasets?persistentId=${persistentId}`) - cy.wait(1500) // Wait for the page to load - - cy.findByText('Files').should('exist') - - cy.findByRole('button', { name: 'Access File' }).should('exist').click() - - // Workaround for issue where Cypress gets stuck on the download - cy.window() - .document() - .then(function (doc) { - doc.addEventListener('click', () => { - setTimeout(function () { - doc.location.reload() - }, 5000) + .its('persistentId') + .then((persistentId: string) => { + cy.wait(1500) // Wait for the dataset to be published + cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.wait(1500) // Wait for the page to load + + cy.findByText('Files').should('exist') + + cy.findByRole('button', { name: 'Access File' }).should('exist').click() + + // Workaround for issue where Cypress gets stuck on the download + cy.window() + .document() + .then(function (doc) { + doc.addEventListener('click', () => { + setTimeout(function () { + doc.location.reload() + }, 5000) + }) + cy.findByText('Plain Text').should('exist').click() }) - cy.findByText('Plain Text').should('exist').click() - }) - cy.findByText('1 Downloads').should('exist') - }) + cy.findByText('1 Downloads').should('exist') + }) + }) + + it('downloads multiple files', () => { + cy.wrap( + DatasetHelper.createWithFiles(FileHelper.createMany(3)).then((dataset) => + DatasetHelper.publish(dataset.persistentId) + ) + ) + .its('persistentId') + .then((persistentId: string) => { + cy.wait(1500) // Wait for the page to load + cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.wait(1500) // Wait for the page to load + + cy.findByText('Files').should('exist') + + cy.get('table > thead > tr > th > input[type=checkbox]').click() + + cy.findByRole('button', { name: 'Download' }).should('exist').click({ force: true }) + + // Workaround for issue where Cypress gets stuck on the download + cy.window() + .document() + .then(function (doc) { + doc.addEventListener('click', () => { + setTimeout(function () { + doc.location.reload() + }, 5000) + }) + cy.findByText('Original Format').should('exist').click() + }) + + cy.findAllByText('1 Downloads').should('exist') + }) + }) }) })