Skip to content

Commit

Permalink
Merge pull request #240 from IQSS/feature/238-integration-single-file…
Browse files Browse the repository at this point in the history
…-download

238 - Integration single file download
  • Loading branch information
GPortas authored Dec 12, 2023
2 parents 9f73f62 + 6610d27 commit 38ceeea
Show file tree
Hide file tree
Showing 16 changed files with 245 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,25 @@ interface DropdownItemProps extends React.HTMLAttributes<HTMLElement> {
href?: string
eventKey?: string
disabled?: boolean
download?: string
children: ReactNode
}

export function DropdownButtonItem({
href,
eventKey,
disabled,
download,
children,
...props
}: DropdownItemProps) {
return (
<DropdownBS.Item href={href} eventKey={eventKey} disabled={disabled} {...props}>
<DropdownBS.Item
href={href}
eventKey={eventKey}
disabled={disabled}
download={download}
{...props}>
{children}
</DropdownBS.Item>
)
Expand Down
13 changes: 10 additions & 3 deletions src/files/domain/models/File.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ export interface FileIngest {
reportMessage?: string
}

export interface FileDownloadUrls {
original: string
tabular?: string
rData?: string
}

export class File {
constructor(
readonly id: number,
Expand All @@ -158,12 +164,13 @@ export class File {
readonly labels: FileLabel[],
public readonly isDeleted: boolean,
public readonly ingest: FileIngest,
readonly checksum?: FileChecksum,
readonly thumbnail?: string,
public readonly downloadUrls: FileDownloadUrls,
public thumbnail?: string,
readonly directory?: string,
readonly embargo?: FileEmbargo,
readonly tabularData?: FileTabularData,
readonly description?: string
readonly description?: string,
readonly checksum?: FileChecksum
) {}

getLink(): string {
Expand Down
14 changes: 12 additions & 2 deletions src/files/infrastructure/mappers/JSFileMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
FileChecksum,
FileDate,
FileDateType,
FileDownloadUrls,
FileEmbargo,
FileIngestStatus,
FileLabel,
Expand Down Expand Up @@ -56,12 +57,13 @@ export class JSFileMapper {
this.toFileLabels(jsFile.categories, jsFile.tabularTags),
this.toFileIsDeleted(jsFile.deleted),
{ status: FileIngestStatus.NONE }, // TODO - Implement this when it is added to js-dataverse
this.toFileChecksum(jsFile.checksum),
this.toFileOriginalFileDownloadUrl(jsFile.id),
this.toFileThumbnail(thumbnail),
this.toFileDirectory(jsFile.directoryLabel),
this.toFileEmbargo(jsFile.embargo),
this.toFileTabularData(jsTabularData),
this.toFileDescription(jsFile.description)
this.toFileDescription(jsFile.description),
this.toFileChecksum(jsFile.checksum)
)
}

Expand Down Expand Up @@ -167,6 +169,14 @@ export class JSFileMapper {
return undefined
}

static toFileOriginalFileDownloadUrl(id: number): FileDownloadUrls {
return {
original: `/api/access/datafile/${id}?format=original`,
tabular: `/api/access/datafile/${id}`,
rData: `/api/access/datafile/${id}?format=RData`
}
}

static toFileThumbnail(thumbnail?: string): string | undefined {
return thumbnail
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ import { File } from '../../../../../../../../files/domain/models/File'
import { FileTabularDownloadOptions } from './FileTabularDownloadOptions'
import { FileNonTabularDownloadOptions } from './FileNonTabularDownloadOptions'
import { useTranslation } from 'react-i18next'
import { useFileDownloadPermission } from '../../../../../../../file/file-permissions/useFileDownloadPermission'

interface FileDownloadOptionsProps {
file: File
}

export function FileDownloadOptions({ file }: FileDownloadOptionsProps) {
const { t } = useTranslation('files')
const { sessionUserHasFileDownloadPermission } = useFileDownloadPermission(file)

if (!sessionUserHasFileDownloadPermission) {
return <></>
}

return (
<>
<DropdownHeader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function FileNonTabularDownloadOptions({ file }: FileNonTabularDownloadOp

return (
<DropdownButtonItem
href={file.downloadUrls.original}
disabled={
file.ingest.status === FileIngestStatus.IN_PROGRESS ||
(dataset && dataset.isLockedFromFileDownload)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ export function FileTabularDownloadOptions({ file }: FileTabularDownloadOptionsP
return (
<>
{originalFileFormatIsKnown && (
<DropdownButtonItem disabled={downloadDisabled}>{`${file.type.original} (${t(
'actions.accessFileMenu.downloadOptions.options.original'
)})`}</DropdownButtonItem>
<DropdownButtonItem href={file.downloadUrls.original} disabled={downloadDisabled}>{`${
file.type.original
} (${t('actions.accessFileMenu.downloadOptions.options.original')})`}</DropdownButtonItem>
)}
<DropdownButtonItem disabled={downloadDisabled}>
<DropdownButtonItem href={file.downloadUrls.tabular} disabled={downloadDisabled}>
{t('actions.accessFileMenu.downloadOptions.options.tabular')}
</DropdownButtonItem>
{file.type.original !== 'R Data' && (
<DropdownButtonItem disabled={downloadDisabled}>
<DropdownButtonItem href={file.downloadUrls.rData} disabled={downloadDisabled}>
{t('actions.accessFileMenu.downloadOptions.options.RData')}
</DropdownButtonItem>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const NonTabularFiles: Story = {
}

export const TabularFiles: Story = {
render: () => <AccessFileMenu file={FileMother.createWithTabularData()} />
render: () => <AccessFileMenu file={FileMother.createTabular()} />
}

export const Restricted: Story = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const WithEmbargo: Story = {
}

export const WithTabularData: Story = {
render: () => <FileInfoCell file={FileMother.createWithTabularData()} />
render: () => <FileInfoCell file={FileMother.createTabular()} />
}

export const WithDescription: Story = {
Expand Down
27 changes: 24 additions & 3 deletions tests/component/files/domain/models/FileMother.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ export class FileMother {
description: valueOrUndefined<string>(faker.lorem.paragraph()),
isDeleted: faker.datatype.boolean(),
ingest: { status: FileIngestStatus.NONE },
downloadUrls: {
original: this.createDownloadUrl(),
tabular: this.createDownloadUrl(),
rData: this.createDownloadUrl()
},
...props
}

Expand All @@ -139,15 +144,23 @@ export class FileMother {
fileMockedData.labels,
fileMockedData.isDeleted,
fileMockedData.ingest,
fileMockedData.checksum,
fileMockedData.downloadUrls,
fileMockedData.thumbnail,
fileMockedData.directory,
fileMockedData.embargo,
fileMockedData.tabularData,
fileMockedData.description
fileMockedData.description,
fileMockedData.checksum
)
}

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 createMany(quantity: number, props?: Partial<File>): File[] {
return Array.from({ length: quantity }).map(() => this.create(props))
}
Expand Down Expand Up @@ -212,7 +225,7 @@ export class FileMother {
})
}

static createWithTabularData(props?: Partial<File>): File {
static createTabular(props?: Partial<File>): File {
return this.createDefault({
type: new FileType('text/tab-separated-values', 'Comma Separated Values'),
tabularData: {
Expand All @@ -224,6 +237,14 @@ export class FileMother {
})
}

static createNonTabular(props?: Partial<File>): File {
return this.createDefault({
type: new FileType('text/plain'),
tabularData: undefined,
...props
})
}

static createWithDescription(): File {
return this.createDefault({
description: faker.lorem.paragraph()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import { AccessFileMenu } from '../../../../../../../../../../src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/AccessFileMenu'
import { FileMother } from '../../../../../../../../files/domain/models/FileMother'
import { Suspense } from 'react'
import { FilePermissionsProvider } from '../../../../../../../../../../src/sections/file/file-permissions/FilePermissionsProvider'
import { FileRepository } from '../../../../../../../../../../src/files/domain/repositories/FileRepository'
import { FileUserPermissionsMother } from '../../../../../../../../files/domain/models/FileUserPermissionsMother'

const file = FileMother.create()

const fileRepository = {} as FileRepository
describe('AccessFileMenu', () => {
beforeEach(() => {
fileRepository.getUserPermissionsById = cy.stub().resolves(
FileUserPermissionsMother.create({
canDownloadFile: true
})
)
})
it('renders the access file menu', () => {
cy.customMount(<AccessFileMenu file={file} />)

Expand Down Expand Up @@ -53,10 +65,11 @@ describe('AccessFileMenu', () => {
})

it('renders the download options header', () => {
const filePublic = FileMother.createWithPublicAccess()
cy.customMount(
<Suspense fallback="loading">
<AccessFileMenu file={file} />
</Suspense>
<FilePermissionsProvider repository={fileRepository}>
<AccessFileMenu file={filePublic} />
</FilePermissionsProvider>
)

cy.findByRole('button', { name: 'Access File' }).click()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,64 @@
import { FileDownloadOptions } from '../../../../../../../../../../src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions'
import { FileMother } from '../../../../../../../../files/domain/models/FileMother'
import { FileType } from '../../../../../../../../../../src/files/domain/models/File'
import { FileUserPermissionsMother } from '../../../../../../../../files/domain/models/FileUserPermissionsMother'
import { FilePermissionsProvider } from '../../../../../../../../../../src/sections/file/file-permissions/FilePermissionsProvider'
import { FileRepository } from '../../../../../../../../../../src/files/domain/repositories/FileRepository'

const fileNonTabular = FileMother.create({
tabularData: undefined,
type: new FileType('text/plain')
})
const fileTabular = FileMother.createWithTabularData()
const fileNonTabular = FileMother.createNonTabular()
const fileTabular = FileMother.createTabular()
const fileRepository = {} as FileRepository
describe('FileDownloadOptions', () => {
beforeEach(() => {
fileRepository.getUserPermissionsById = cy.stub().resolves(
FileUserPermissionsMother.create({
canDownloadFile: true
})
)
})

it('renders the download options header', () => {
cy.customMount(<FileDownloadOptions file={fileNonTabular} />)
cy.customMount(
<FilePermissionsProvider repository={fileRepository}>
<FileDownloadOptions file={fileNonTabular} />
</FilePermissionsProvider>
)

cy.findByRole('heading', { name: 'Download Options' }).should('exist')
})

it('does not render the download options if the user does not have permissions', () => {
fileRepository.getUserPermissionsById = cy.stub().resolves(
FileUserPermissionsMother.create({
canDownloadFile: false
})
)

cy.customMount(
<FilePermissionsProvider repository={fileRepository}>
<FileDownloadOptions file={fileNonTabular} />
</FilePermissionsProvider>
)

cy.findByRole('heading', { name: 'Download Options' }).should('not.exist')
})

it('renders the download options for a non-tabular file', () => {
cy.customMount(<FileDownloadOptions file={fileNonTabular} />)
cy.customMount(
<FilePermissionsProvider repository={fileRepository}>
<FileDownloadOptions file={fileNonTabular} />{' '}
</FilePermissionsProvider>
)

cy.findByRole('button', { name: 'Plain Text' }).should('exist')
cy.findByRole('link', { name: 'Plain Text' }).should('exist')
})

it('renders the download options for a tabular file', () => {
cy.customMount(<FileDownloadOptions file={fileTabular} />)

cy.findByRole('button', { name: 'Comma Separated Values (Original File Format)' }).should(
'exist'
cy.customMount(
<FilePermissionsProvider repository={fileRepository}>
<FileDownloadOptions file={fileTabular} />
</FilePermissionsProvider>
)

cy.findByRole('link', { name: 'Comma Separated Values (Original File Format)' }).should('exist')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,27 @@ describe('FileNonTabularDownloadOptions', () => {
})
cy.customMount(<FileNonTabularDownloadOptions file={fileNonTabularUnknown} />)

cy.findByRole('button', { name: 'Original File Format' })
cy.findByRole('link', { name: 'Original File Format' })
.should('exist')
.should('not.have.class', 'disabled')
.should('have.attr', 'href', fileNonTabularUnknown.downloadUrls.original)
})

it('renders the download options for a non-tabular file', () => {
cy.customMount(<FileNonTabularDownloadOptions file={fileNonTabular} />)

cy.findByRole('button', { name: 'Plain Text' })
cy.findByRole('link', { name: 'Plain Text' })
.should('exist')
.should('not.have.class', 'disabled')
.should('have.attr', 'href', fileNonTabular.downloadUrls.original)
})

it('does not render the download options for a tabular file', () => {
const fileTabular = FileMother.createWithTabularData()
const fileTabular = FileMother.createTabular()
cy.customMount(<FileNonTabularDownloadOptions file={fileTabular} />)

cy.findByRole('button', { name: 'Original File Format' }).should('not.exist')
cy.findByRole('button', { name: 'Tab-Delimited' }).should('not.exist')
cy.findByRole('link', { name: 'Original File Format' }).should('not.exist')
cy.findByRole('link', { name: 'Tab-Delimited' }).should('not.exist')
})

it('renders the options as disabled when the file ingest is in progress', () => {
Expand All @@ -54,7 +56,7 @@ describe('FileNonTabularDownloadOptions', () => {
})
cy.customMount(<FileNonTabularDownloadOptions file={fileNonTabularInProgress} />)

cy.findByRole('button', { name: 'Plain Text' }).should('have.class', 'disabled')
cy.findByRole('link', { name: 'Plain Text' }).should('have.class', 'disabled')
})

it('renders the options as disabled when the dataset is locked from file download', () => {
Expand All @@ -72,6 +74,6 @@ describe('FileNonTabularDownloadOptions', () => {
</DatasetProvider>
)

cy.findByRole('button', { name: 'Plain Text' }).should('have.class', 'disabled')
cy.findByRole('link', { name: 'Plain Text' }).should('have.class', 'disabled')
})
})
Loading

0 comments on commit 38ceeea

Please sign in to comment.