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

187 - Files table download button #209

Merged
merged 6 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
12 changes: 12 additions & 0 deletions public/locales/en/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -120,6 +125,13 @@
"provenance": "Provenance",
"delete": "Delete"
}
},
"downloadFiles": {
"title": "Download",
"options": {
"original": "Original Format",
"archival": "Archival Format (.tab)"
}
}
},
"requestAccess": {
Expand Down
4 changes: 4 additions & 0 deletions src/files/domain/models/File.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,8 @@ export class File {
}
return false
}

get isTabularData(): boolean {
return this.tabularData !== undefined
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<File>[] => [
export const createColumnsDefinition = (
paginationInfo: FilePaginationInfo,
fileSelection: FileSelection
): ColumnDef<File>[] => [
{
id: 'select',
header: ({ table }) => (
Expand Down Expand Up @@ -38,7 +42,10 @@ export const createColumnsDefinition = (paginationInfo: FilePaginationInfo): Col
},
{
header: ({ table }) => (
<FileActionsHeader files={table.getRowModel().rows.map((row) => row.original)} />
<FileActionsHeader
files={table.getRowModel().rows.map((row) => row.original)}
fileSelection={fileSelection}
/>
),
accessorKey: 'status',
cell: (props) => <FileActionsCell file={props.row.original} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.container {
display: flex;
justify-content: end;
text-align: right;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ import { EditFilesMenu } from './edit-files-menu/EditFilesMenu'
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 (
<div aria-label={t('actions.title')} className={styles.container}>
<EditFilesMenu files={files} />
<EditFilesMenu files={files} fileSelection={fileSelection} />
<DownloadFilesButton files={files} fileSelection={fileSelection} />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.icon {
margin-right: 0.3rem;
margin-bottom: 0.2rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { File } 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'

interface DownloadFilesButtonProps {
files: File[]
fileSelection: FileSelection
}

const MINIMUM_FILES_COUNT_TO_SHOW_DOWNLOAD_FILES_BUTTON = 1
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 ||
!dataset?.permissions.canDownloadFiles
) {
return <></>
}

const onClick = () => {
if (Object.keys(fileSelection).length === SELECTED_FILES_EMPTY) {
setShowNoFilesSelectedModal(true)
}
}

if (files.some((file) => file.isTabularData)) {
return (
<>
<DropdownButton
id="download-files"
icon={<Download className={styles.icon} />}
title={t('actions.downloadFiles.title')}
variant="secondary"
withSpacing>
<DropdownButtonItem onClick={onClick}>
{t('actions.downloadFiles.options.original')}
</DropdownButtonItem>
<DropdownButtonItem onClick={onClick}>
{t('actions.downloadFiles.options.archival')}
</DropdownButtonItem>
</DropdownButton>
<NoSelectedFilesModal
show={showNoFilesSelectedModal}
handleClose={() => setShowNoFilesSelectedModal(false)}
/>
</>
)
}

return (
<>
<Button
variant="secondary"
icon={<Download className={styles.icon} />}
withSpacing
onClick={onClick}>
{t('actions.downloadFiles.title')}
</Button>
<NoSelectedFilesModal
show={showNoFilesSelectedModal}
handleClose={() => setShowNoFilesSelectedModal(false)}
/>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -30,7 +32,7 @@ export function EditFilesMenu({ files }: EditFilesMenuProps) {
title={t('actions.editFilesMenu.title')}
disabled={dataset.isLockedFromEdits || !dataset.hasValidTermsOfAccess}
icon={<PencilFill className={styles.icon} />}>
<EditFilesOptions files={files} />
<EditFilesOptions files={files} fileSelection={fileSelection} />
</DropdownButton>
)
}
Original file line number Diff line number Diff line change
@@ -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 (
<>
<DropdownButtonItem>{t('actions.editFilesMenu.options.metadata')}</DropdownButtonItem>
<DropdownButtonItem onClick={onClick}>
{t('actions.editFilesMenu.options.metadata')}
</DropdownButtonItem>
{files.some((file) => file.access.restricted) && (
<DropdownButtonItem>{t('actions.editFilesMenu.options.unrestrict')}</DropdownButtonItem>
<DropdownButtonItem onClick={onClick}>
{t('actions.editFilesMenu.options.unrestrict')}
</DropdownButtonItem>
)}
{files.some((file) => !file.access.restricted) && (
<DropdownButtonItem>{t('actions.editFilesMenu.options.restrict')}</DropdownButtonItem>
<DropdownButtonItem onClick={onClick}>
{t('actions.editFilesMenu.options.restrict')}
</DropdownButtonItem>
)}
<DropdownButtonItem>{t('actions.editFilesMenu.options.replace')}</DropdownButtonItem>
<DropdownButtonItem onClick={onClick}>
{t('actions.editFilesMenu.options.replace')}
</DropdownButtonItem>
{settingsEmbargoAllowed && (
<DropdownButtonItem>{t('actions.editFilesMenu.options.embargo')}</DropdownButtonItem>
<DropdownButtonItem onClick={onClick}>
{t('actions.editFilesMenu.options.embargo')}
</DropdownButtonItem>
)}
{provenanceEnabledByConfig && (
<DropdownButtonItem>{t('actions.editFilesMenu.options.provenance')}</DropdownButtonItem>
<DropdownButtonItem onClick={onClick}>
{t('actions.editFilesMenu.options.provenance')}
</DropdownButtonItem>
)}
<DropdownButtonItem>{t('actions.editFilesMenu.options.delete')}</DropdownButtonItem>
<DropdownButtonItem onClick={onClick}>
{t('actions.editFilesMenu.options.delete')}
</DropdownButtonItem>
<NoSelectedFilesModal
show={showNoFilesSelectedModal}
handleClose={() => setShowNoFilesSelectedModal(false)}
/>
</>
)
}
Original file line number Diff line number Diff line change
@@ -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 (
<Modal show={show} onHide={handleClose} size="lg">
<Modal.Header>
<Modal.Title>{t('actions.noSelectedFilesAlert.title')}</Modal.Title>
</Modal.Header>
<Modal.Body>
<p className={styles.paragraph}>
<ExclamationCircleFill /> {t('actions.noSelectedFilesAlert.message')}
</p>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
{t('actions.noSelectedFilesAlert.close')}
</Button>
</Modal.Footer>
</Modal>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Meta, StoryObj } from '@storybook/react'
import { EditFilesMenu } from '../../../../../../sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu'
import { WithI18next } from '../../../../../WithI18next'
import { WithSettings } from '../../../../../WithSettings'
import { WithLoggedInUser } from '../../../../../WithLoggedInUser'
import { WithDatasetAllPermissionsGranted } from '../../../../WithDatasetAllPermissionsGranted'
import { FileMother } from '../../../../../../../tests/component/files/domain/models/FileMother'
import { DownloadFilesButton } from '../../../../../../sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton'

const meta: Meta<typeof EditFilesMenu> = {
title: 'Sections/Dataset Page/DatasetFiles/FilesTable/DownloadFilesButton',
component: EditFilesMenu,
decorators: [WithI18next, WithSettings, WithLoggedInUser, WithDatasetAllPermissionsGranted]
}

export default meta
type Story = StoryObj<typeof EditFilesMenu>

export const NonTabularFiles: Story = {
render: () => (
<DownloadFilesButton
files={FileMother.createMany(2, { tabularData: undefined })}
fileSelection={{}}
/>
)
}

export const TabularFiles: Story = {
render: () => (
<DownloadFilesButton
files={FileMother.createMany(2, {
tabularData: {
variablesCount: 2,
observationsCount: 3,
unf: 'some-unf'
}
})}
fileSelection={{}}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ export default meta
type Story = StoryObj<typeof EditFilesMenu>

export const Default: Story = {
render: () => <EditFilesMenu files={FileMother.createMany(2)} />
render: () => <EditFilesMenu files={FileMother.createMany(2)} fileSelection={{}} />
}
4 changes: 2 additions & 2 deletions tests/component/files/domain/models/FileMother.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ export class FileMother {
)
}

static createMany(quantity: number): File[] {
return Array.from({ length: quantity }).map(() => this.create())
static createMany(quantity: number, props?: Partial<File>): File[] {
return Array.from({ length: quantity }).map(() => this.create(props))
}

static createDefault(props?: Partial<File>): File {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('FileActionsHeader', () => {
it('renders the file actions header', () => {
const datasetRepository: DatasetRepository = {} as DatasetRepository
const datasetWithUpdatePermissions = DatasetMother.create({
permissions: DatasetPermissionsMother.createWithUpdateDatasetAllowed(),
permissions: DatasetPermissionsMother.createWithAllAllowed(),
hasValidTermsOfAccess: true
})
datasetRepository.getByPersistentId = cy.stub().resolves(datasetWithUpdatePermissions)
Expand All @@ -20,10 +20,11 @@ describe('FileActionsHeader', () => {
<DatasetProvider
repository={datasetRepository}
searchParams={{ persistentId: 'some-persistent-id', version: 'some-version' }}>
<FileActionsHeader files={files} />
<FileActionsHeader files={files} fileSelection={{}} />
</DatasetProvider>
)

cy.findByRole('button', { name: 'Edit Files' }).should('exist')
cy.findByRole('button', { name: 'Download' }).should('exist')
})
})
Loading
Loading