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

238 - Integration single file download #240

Merged
merged 7 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading