Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

221 - Integration multiple files download #254

Merged
8 changes: 8 additions & 0 deletions src/dataset/domain/models/Dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,11 @@ export interface PrivateUrl {
urlSnippet: string
}

export interface DatasetDownloadUrls {
original: string
archival: string
}

export class Dataset {
constructor(
public readonly persistentId: string,
Expand All @@ -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[]
Expand Down Expand Up @@ -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[]
Expand Down Expand Up @@ -475,6 +482,7 @@ export class Dataset {
this.hasOneTabularFileAtLeast,
this.isValid,
this.isReleased,
this.downloadUrls,
this.thumbnail,
this.privateUrl,
this.fileDownloadSizes
Expand Down
22 changes: 19 additions & 3 deletions src/dataset/infrastructure/mappers/JSDatasetMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import {
DatasetMetadataFields,
DatasetVersion,
MetadataBlockName,
PrivateUrl,
DatasetDownloadUrls,
DatasetPermissions,
DatasetLock,
DatasetLockReason,
PrivateUrl
DatasetLockReason
} from '../../domain/models/Dataset'

export class JSDatasetMapper {
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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 &&
Expand Down
13 changes: 6 additions & 7 deletions src/files/domain/models/File.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -127,6 +121,11 @@ export interface FileTabularData {
unf?: string
}

export enum FileDownloadMode {
ARCHIVAL = 'archival',
ORIGINAL = 'original'
}

export enum FileLabelType {
CATEGORY = 'category',
TAG = 'tag'
Expand Down
4 changes: 3 additions & 1 deletion src/files/domain/repositories/FileRepository.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -23,4 +23,6 @@ export interface FileRepository {
criteria?: FileCriteria
) => Promise<number>
getUserPermissionsById: (id: number) => Promise<FileUserPermissions>
getMultipleFileDownloadUrl: (ids: number[], downloadMode: FileDownloadMode) => string
getFileDownloadUrl: (id: number, downloadMode: FileDownloadMode) => string
}
15 changes: 15 additions & 0 deletions src/files/domain/useCases/getMultipleFileDownloadUrl.ts
Original file line number Diff line number Diff line change
@@ -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)
}
21 changes: 16 additions & 5 deletions src/files/infrastructure/FileJSDataverseRepository.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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}`
}
}
29 changes: 16 additions & 13 deletions src/sections/dataset/DatasetFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -22,19 +23,21 @@ const settingRepository = new SettingJSDataverseRepository()
export class DatasetFactory {
static create(): ReactElement {
return (
<FilePermissionsProvider repository={fileRepository}>
<SettingsProvider repository={settingRepository}>
<NotImplementedModalProvider>
<MetadataBlockInfoProvider repository={metadataBlockInfoRepository}>
<AnonymizedProvider>
<AlertProvider>
<DatasetWithSearchParams />
</AlertProvider>
</AnonymizedProvider>
</MetadataBlockInfoProvider>
</NotImplementedModalProvider>
</SettingsProvider>
</FilePermissionsProvider>
<MultipleFileDownloadProvider repository={fileRepository}>
<FilePermissionsProvider repository={fileRepository}>
<SettingsProvider repository={settingRepository}>
<NotImplementedModalProvider>
<MetadataBlockInfoProvider repository={metadataBlockInfoRepository}>
<AnonymizedProvider>
<AlertProvider>
<DatasetWithSearchParams />
</AlertProvider>
</AnonymizedProvider>
</MetadataBlockInfoProvider>
</NotImplementedModalProvider>
</SettingsProvider>
</FilePermissionsProvider>
</MultipleFileDownloadProvider>
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Expand All @@ -47,19 +47,19 @@ export function AccessDatasetMenu({
const DatasetDownloadOptions = ({ datasetContainsTabularFiles }: DatasetDownloadOptionsProps) => {
return datasetContainsTabularFiles ? (
<>
<DropdownButtonItem onClick={() => handleDownload(FileDownloadSizeMode.ORIGINAL)}>
<DropdownButtonItem onClick={() => handleDownload(FileDownloadMode.ORIGINAL)}>
{t('datasetActionButtons.accessDataset.downloadOriginalZip')} (
{getFormattedFileSize(FileDownloadSizeMode.ORIGINAL)})
{getFormattedFileSize(FileDownloadMode.ORIGINAL)})
</DropdownButtonItem>
<DropdownButtonItem onClick={() => handleDownload(FileDownloadSizeMode.ARCHIVAL)}>
<DropdownButtonItem onClick={() => handleDownload(FileDownloadMode.ARCHIVAL)}>
{t('datasetActionButtons.accessDataset.downloadArchiveZip')} (
{getFormattedFileSize(FileDownloadSizeMode.ARCHIVAL)})
{getFormattedFileSize(FileDownloadMode.ARCHIVAL)})
</DropdownButtonItem>
</>
) : (
<DropdownButtonItem onClick={() => handleDownload(FileDownloadSizeMode.ORIGINAL)}>
<DropdownButtonItem onClick={() => handleDownload(FileDownloadMode.ORIGINAL)}>
{t('datasetActionButtons.accessDataset.downloadZip')} (
{getFormattedFileSize(FileDownloadSizeMode.ORIGINAL)})
{getFormattedFileSize(FileDownloadMode.ORIGINAL)})
</DropdownButtonItem>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
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'
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[]
Expand All @@ -21,27 +21,31 @@ 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<HTMLButtonElement>) => {
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
) {
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 (
<>
<DropdownButton
Expand All @@ -50,10 +54,10 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto
title={t('actions.downloadFiles.title')}
variant="secondary"
withSpacing>
<DropdownButtonItem onClick={onClick}>
<DropdownButtonItem onClick={onClick} href={getDownloadUrl(FileDownloadMode.ORIGINAL)}>
{t('actions.downloadFiles.options.original')}
</DropdownButtonItem>
<DropdownButtonItem onClick={onClick}>
<DropdownButtonItem onClick={onClick} href={getDownloadUrl(FileDownloadMode.ARCHIVAL)}>
{t('actions.downloadFiles.options.archival')}
</DropdownButtonItem>
</DropdownButton>
Expand All @@ -67,17 +71,25 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto

return (
<>
<Button
variant="secondary"
icon={<Download className={styles.icon} />}
withSpacing
onClick={onClick}>
{t('actions.downloadFiles.title')}
</Button>
<a href={getDownloadUrl(FileDownloadMode.ORIGINAL)}>
<Button
variant="secondary"
icon={<Download className={styles.icon} />}
withSpacing
onClick={onClick}>
{t('actions.downloadFiles.title')}
</Button>
</a>
<NoSelectedFilesModal
show={showNoFilesSelectedModal}
handleClose={() => setShowNoFilesSelectedModal(false)}
/>
</>
)
}

const getFileIdsFromSelection = (fileSelection: FileSelection): number[] => {
return Object.values(fileSelection)
.filter((file): file is File => file !== undefined)
.map((file) => file.id)
}
Loading