Skip to content

Commit

Permalink
Merge pull request #209 from IQSS/feature/187-files-table-download-bu…
Browse files Browse the repository at this point in the history
…tton

187 - Files table download button
  • Loading branch information
GPortas authored Nov 2, 2023
2 parents 668f39e + 9675c50 commit 97695ea
Show file tree
Hide file tree
Showing 18 changed files with 440 additions and 36 deletions.
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

0 comments on commit 97695ea

Please sign in to comment.