diff --git a/src/files/domain/models/File.ts b/src/files/domain/models/File.ts index caba31701..34f18281d 100644 --- a/src/files/domain/models/File.ts +++ b/src/files/domain/models/File.ts @@ -73,13 +73,26 @@ export interface FileLabel { value: string } +export class FileType { + constructor(readonly value: string) {} + + toDisplayFormat(): string { + const words = this.value.split(' ') + return words + .map((word) => { + return word[0].toUpperCase() + word.substring(1) + }) + .join(' ') + } +} + export class File { constructor( readonly id: string, readonly version: FileVersion, readonly name: string, readonly access: FileAccess, - readonly type: string, + readonly type: FileType, readonly size: FileSize, readonly date: FileDate, readonly downloads: number, diff --git a/src/files/domain/models/FileCriteria.ts b/src/files/domain/models/FileCriteria.ts index 1b96fbb63..23aaea33d 100644 --- a/src/files/domain/models/FileCriteria.ts +++ b/src/files/domain/models/FileCriteria.ts @@ -1,5 +1,16 @@ -export interface FileCriteria { - sortBy?: FileSortByOption +export class FileCriteria { + constructor( + public readonly sortBy: FileSortByOption = FileSortByOption.NAME_AZ, + public readonly filterByType: string = 'All' + ) {} + + withSortBy(sortBy: FileSortByOption): FileCriteria { + return new FileCriteria(sortBy, this.filterByType) + } + + withFilterByType(filterByType: string): FileCriteria { + return new FileCriteria(this.sortBy, filterByType) + } } export enum FileSortByOption { diff --git a/src/sections/dataset/dataset-files/DatasetFiles.tsx b/src/sections/dataset/dataset-files/DatasetFiles.tsx index 67e88f812..cd3b01639 100644 --- a/src/sections/dataset/dataset-files/DatasetFiles.tsx +++ b/src/sections/dataset/dataset-files/DatasetFiles.tsx @@ -13,13 +13,15 @@ interface DatasetFilesProps { datasetVersion?: string } +const MINIMUM_FILES_TO_SHOW_CRITERIA_INPUTS = 2 + export function DatasetFiles({ filesRepository, datasetPersistentId, datasetVersion }: DatasetFilesProps) { - const [criteria, setCriteria] = useState() - const MINIMUM_FILES_TO_SHOW_CRITERIA_INPUTS = 2 + const [criteria, setCriteria] = useState(new FileCriteria()) + const { files, isLoading } = useFiles( filesRepository, datasetPersistentId, @@ -28,7 +30,7 @@ export function DatasetFiles({ ) const { table, setFilesTableData } = useFilesTable() const handleCriteriaChange = (newCriteria: FileCriteria) => { - setCriteria((criteria) => ({ ...criteria, ...newCriteria })) + setCriteria(newCriteria) } useEffect(() => { @@ -42,7 +44,7 @@ export function DatasetFiles({ return ( <> {files.length >= MINIMUM_FILES_TO_SHOW_CRITERIA_INPUTS && ( - + )} diff --git a/src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaFilters.tsx b/src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaFilters.tsx new file mode 100644 index 000000000..efdb54388 --- /dev/null +++ b/src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaFilters.tsx @@ -0,0 +1,48 @@ +import { FileCriteria } from '../../../../files/domain/models/FileCriteria' +import { DropdownButton, DropdownButtonItem } from 'dataverse-design-system' +import { FileType } from '../../../../files/domain/models/File' + +export function FileCriteriaFilters({ + criteria, + onCriteriaChange +}: { + criteria: FileCriteria + onCriteriaChange: (criteria: FileCriteria) => void +}) { + const totalFilesInfo = { + totalCount: 222, + countPerType: [ + { + type: new FileType('text'), + count: 5 + }, + { + type: new FileType('image'), + count: 485 + } + ] + } // TODO: Get from API + const handleTypeChange = (eventKey: string | null) => { + onCriteriaChange(criteria.withFilterByType(eventKey as string)) + } + + return ( + <> + Filter by: + + {/* TODO: All bold if selected*/} + All + {/* TODO: Add separator to design system*/} + {totalFilesInfo.countPerType.map(({ type, count }) => ( + + {`${type.toDisplayFormat()} (${count})`} + + ))} + + + ) +} diff --git a/src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaInputs.tsx b/src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaInputs.tsx index 226f11011..99dc1628e 100644 --- a/src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaInputs.tsx +++ b/src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaInputs.tsx @@ -2,16 +2,22 @@ import { FileCriteria } from '../../../../files/domain/models/FileCriteria' import { Col, Row } from 'dataverse-design-system' import styles from './FileCriteriaInputs.module.scss' import { FileCriteriaSortBy } from './FileCriteriaSortBy' +import { FileCriteriaFilters } from './FileCriteriaFilters' export function FileCriteriaInputs({ + criteria, onCriteriaChange }: { + criteria: FileCriteria onCriteriaChange: (criteria: FileCriteria) => void }) { return ( + + + - + ) diff --git a/src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaSortBy.tsx b/src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaSortBy.tsx index e32b80e74..b17438cfa 100644 --- a/src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaSortBy.tsx +++ b/src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaSortBy.tsx @@ -5,13 +5,15 @@ import { DropdownButton, DropdownButtonItem } from 'dataverse-design-system' import { useTranslation } from 'react-i18next' export function FileCriteriaSortBy({ + criteria, onCriteriaChange }: { + criteria: FileCriteria onCriteriaChange: (criteria: FileCriteria) => void }) { const { t } = useTranslation('files') const handleSortChange = (eventKey: string | null) => { - onCriteriaChange({ sortBy: eventKey as FileSortByOption }) + onCriteriaChange(criteria.withSortBy(eventKey as FileSortByOption)) } return ( diff --git a/src/sections/dataset/dataset-files/files-table/file-info-cell/FileType.tsx b/src/sections/dataset/dataset-files/files-table/file-info-cell/FileType.tsx index 70b196279..576534780 100644 --- a/src/sections/dataset/dataset-files/files-table/file-info-cell/FileType.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-info-cell/FileType.tsx @@ -1,7 +1,7 @@ -import { FileSize } from '../../../../../files/domain/models/File' +import { FileSize, FileType as FileTypeModel } from '../../../../../files/domain/models/File' interface FileTypeProps { - type: string + type: FileTypeModel size: FileSize } @@ -9,15 +9,8 @@ export function FileType({ type, size }: FileTypeProps) { return (
- {capitalizeFirstLetter(type)} - {size.toString()} + {type.toDisplayFormat()} - {size.toString()}
) } - -function capitalizeFirstLetter(str: string): string { - if (str.length === 0) { - return str - } - return str.charAt(0).toUpperCase() + str.slice(1) -} diff --git a/src/sections/dataset/dataset-files/files-table/file-info-cell/file-thumbnail/FileThumbnail.tsx b/src/sections/dataset/dataset-files/files-table/file-info-cell/file-thumbnail/FileThumbnail.tsx index 9a6eaa224..c4ac0bff7 100644 --- a/src/sections/dataset/dataset-files/files-table/file-info-cell/file-thumbnail/FileThumbnail.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-info-cell/file-thumbnail/FileThumbnail.tsx @@ -1,12 +1,12 @@ import { FileThumbnailIcon } from './FileThumbnailIcon' import { FileThumbnailPreviewImage } from './FileThumbnailPreviewImage' -import { FileAccess } from '../../../../../../files/domain/models/File' +import { FileAccess, FileType } from '../../../../../../files/domain/models/File' import { FileThumbnailRestrictedIcon } from './FileThumbnailRestrictedIcon' interface FileThumbnailProps { thumbnail?: string | undefined name: string - type: string + type: FileType access: FileAccess } diff --git a/src/sections/dataset/dataset-files/files-table/file-info-cell/file-thumbnail/FileThumbnailIcon.tsx b/src/sections/dataset/dataset-files/files-table/file-info-cell/file-thumbnail/FileThumbnailIcon.tsx index ab2d8ca50..800a0f228 100644 --- a/src/sections/dataset/dataset-files/files-table/file-info-cell/file-thumbnail/FileThumbnailIcon.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-info-cell/file-thumbnail/FileThumbnailIcon.tsx @@ -1,5 +1,6 @@ import styles from './FileThumbnail.module.scss' import { IconName } from 'dataverse-design-system' +import { FileType } from '../../../../../../files/domain/models/File' const TYPE_TO_ICON: Record = { archive: IconName.PACKAGE, @@ -19,8 +20,8 @@ const TYPE_TO_ICON: Record = { other: IconName.OTHER } -export function FileThumbnailIcon({ type }: { type: string }) { - const icon = TYPE_TO_ICON[type] || TYPE_TO_ICON.default +export function FileThumbnailIcon({ type }: { type: FileType }) { + const icon = TYPE_TO_ICON[type.value] || TYPE_TO_ICON.default return (
diff --git a/src/stories/dataset/dataset-files/files-table/file-info-cell/file-thumbnail/FileThumbnail.stories.tsx b/src/stories/dataset/dataset-files/files-table/file-info-cell/file-thumbnail/FileThumbnail.stories.tsx index 06da5cdc1..b02b93a70 100644 --- a/src/stories/dataset/dataset-files/files-table/file-info-cell/file-thumbnail/FileThumbnail.stories.tsx +++ b/src/stories/dataset/dataset-files/files-table/file-info-cell/file-thumbnail/FileThumbnail.stories.tsx @@ -3,6 +3,7 @@ import { faker } from '@faker-js/faker' import { FileThumbnail } from '../../../../../../sections/dataset/dataset-files/files-table/file-info-cell/file-thumbnail/FileThumbnail' import { WithI18next } from '../../../../../WithI18next' import { FileMother } from '../../../../../../../tests/component/files/domain/models/FileMother' +import { FileType } from '../../../../../../files/domain/models/File' const meta: Meta = { title: 'Sections/Dataset Page/DatasetFiles/FilesTable/FileInfoCell/FileThumbnail', @@ -16,7 +17,7 @@ type Story = StoryObj export const WithIcon: Story = { render: () => { const file = FileMother.create({ - type: 'some-type', + type: new FileType('some-type'), access: { restricted: false, canDownload: true }, thumbnail: undefined }) diff --git a/tests/component/files/domain/models/FileMother.ts b/tests/component/files/domain/models/FileMother.ts index 278d1fe06..a388b83f1 100644 --- a/tests/component/files/domain/models/FileMother.ts +++ b/tests/component/files/domain/models/FileMother.ts @@ -8,6 +8,7 @@ import { FileSize, FileSizeUnit, FileStatus, + FileType, FileVersion } from '../../../../../src/files/domain/models/File' @@ -80,7 +81,7 @@ export class FileMother { ), fileMockedData.name, fileMockedData.access, - fileMockedData.type, + new FileType(fileMockedData.type as string), new FileSize(fileMockedData.size.value, fileMockedData.size.unit), fileMockedData.date, fileMockedData.downloads, @@ -100,7 +101,7 @@ export class FileMother { static createDefault(props?: Partial): File { const defaultFile = { - type: 'file', + type: new FileType('file'), version: { majorNumber: 1, minorNumber: 0, @@ -145,7 +146,7 @@ export class FileMother { static createWithTabularData(): File { return this.createDefault({ - type: 'tabular data', + type: new FileType('tabular data'), tabularData: { variablesCount: faker.datatype.number(100), observationsCount: faker.datatype.number(100), diff --git a/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx b/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx index 67c875e88..751eadb5b 100644 --- a/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx @@ -1,6 +1,7 @@ import { FileMother } from '../../../files/domain/models/FileMother' import { DatasetFiles } from '../../../../../src/sections/dataset/dataset-files/DatasetFiles' import { FileRepository } from '../../../../../src/files/domain/repositories/FileRepository' +import { FileCriteria, FileSortByOption } from '../../../../../src/files/domain/models/FileCriteria' const testFiles = FileMother.createMany(200) const datasetPersistentId = 'test-dataset-persistent-id' @@ -103,8 +104,10 @@ describe('DatasetFiles', () => { 'be.calledWith', datasetPersistentId, datasetVersion, - { sortBy: 'name_az' } + new FileCriteria().withSortBy(FileSortByOption.NAME_AZ) ) + + cy.findByRole('button', { name: 'Filter Type: All' }).should('exist') }) it('does not render the files criteria inputs when there are less than 2 files', () => { @@ -119,5 +122,6 @@ describe('DatasetFiles', () => { ) cy.findByRole('button', { name: /Sort/ }).should('not.exist') + cy.findByRole('button', { name: 'Filter Type: All' }).should('not.exist') }) }) diff --git a/tests/component/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaFilters.spec.tsx b/tests/component/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaFilters.spec.tsx new file mode 100644 index 000000000..95e6d30e8 --- /dev/null +++ b/tests/component/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaFilters.spec.tsx @@ -0,0 +1,21 @@ +import { FileCriteria } from '../../../../../../src/files/domain/models/FileCriteria' +import { FileCriteriaFilters } from '../../../../../../src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaFilters' + +const defaultCriteria = new FileCriteria() +describe('FilesCriteriaFilters', () => { + it('renders filter by type options', () => { + const onCriteriaChange = cy.stub().as('onCriteriaChange') + + cy.customMount( + + ) + + cy.findByText('Filter by:').should('exist') + + cy.findByRole('button', { name: 'Filter Type: All' }).click() + + cy.findByText('All').should('exist') + cy.findByText('Image (485)').should('exist') + cy.findByText('Text (5)').should('exist') + }) +}) diff --git a/tests/component/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaInputs.spec.tsx b/tests/component/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaInputs.spec.tsx index fcf290dee..c092905a4 100644 --- a/tests/component/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaInputs.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaInputs.spec.tsx @@ -1,10 +1,13 @@ import { FileCriteriaInputs } from '../../../../../../src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaInputs' +import { FileCriteria } from '../../../../../../src/files/domain/models/FileCriteria' describe('FileCriteriaInputs', () => { it('renders the SortBy input', () => { const onCriteriaChange = cy.stub().as('onCriteriaChange') - cy.customMount() + cy.customMount( + + ) cy.findByRole('button', { name: /Sort/ }).should('exist') }) diff --git a/tests/component/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaSortBy.spec.tsx b/tests/component/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaSortBy.spec.tsx index dcb00f8e9..176056813 100644 --- a/tests/component/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaSortBy.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaSortBy.spec.tsx @@ -1,33 +1,58 @@ import { FileCriteriaSortBy } from '../../../../../../src/sections/dataset/dataset-files/file-criteria-inputs/FileCriteriaSortBy' +import { + FileCriteria, + FileSortByOption +} from '../../../../../../src/files/domain/models/FileCriteria' +const defaultCriteria = new FileCriteria() describe('FilesCriteriaSortBy', () => { it('calls onCriteriaChange with the selected orderBy value', () => { const onCriteriaChange = cy.stub().as('onCriteriaChange') - cy.customMount() + cy.customMount( + + ) cy.findByRole('button', { name: /Sort/ }).click() cy.findByText('Name (A-Z)').should('exist').click() - cy.wrap(onCriteriaChange).should('be.calledWith', { sortBy: 'name_az' }) + cy.wrap(onCriteriaChange).should( + 'be.calledWith', + defaultCriteria.withSortBy(FileSortByOption.NAME_AZ) + ) cy.findByRole('button', { name: /Sort/ }).click() cy.findByText('Name (Z-A)').click() - cy.wrap(onCriteriaChange).should('be.calledWith', { sortBy: 'name_za' }) + cy.wrap(onCriteriaChange).should( + 'be.calledWith', + defaultCriteria.withSortBy(FileSortByOption.NAME_ZA) + ) cy.findByRole('button', { name: /Sort/ }).click() cy.findByText('Newest').click() - cy.wrap(onCriteriaChange).should('be.calledWith', { sortBy: 'newest' }) + cy.wrap(onCriteriaChange).should( + 'be.calledWith', + defaultCriteria.withSortBy(FileSortByOption.NEWEST) + ) cy.findByRole('button', { name: /Sort/ }).click() cy.findByText('Oldest').click() - cy.wrap(onCriteriaChange).should('be.calledWith', { sortBy: 'oldest' }) + cy.wrap(onCriteriaChange).should( + 'be.calledWith', + defaultCriteria.withSortBy(FileSortByOption.OLDEST) + ) cy.findByRole('button', { name: /Sort/ }).click() cy.findByText('Size').click() - cy.wrap(onCriteriaChange).should('be.calledWith', { sortBy: 'size' }) + cy.wrap(onCriteriaChange).should( + 'be.calledWith', + defaultCriteria.withSortBy(FileSortByOption.SIZE) + ) cy.findByRole('button', { name: /Sort/ }).click() cy.findByText('Type').click() - cy.wrap(onCriteriaChange).should('be.calledWith', { sortBy: 'type' }) + cy.wrap(onCriteriaChange).should( + 'be.calledWith', + defaultCriteria.withSortBy(FileSortByOption.TYPE) + ) }) }) diff --git a/tests/component/sections/dataset/dataset-files/files-table/files-info-cell/FileType.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/files-info-cell/FileType.spec.tsx index 13f99017c..78f2aa631 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/files-info-cell/FileType.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/files-info-cell/FileType.spec.tsx @@ -6,13 +6,6 @@ describe('FileType', () => { const file = FileMother.create() cy.customMount() - cy.findByText(`${capitalizeFirstLetter(file.type)} - ${file.size.toString()}`).should('exist') + cy.findByText(`${file.type.toDisplayFormat()} - ${file.size.toString()}`).should('exist') }) }) - -function capitalizeFirstLetter(str: string): string { - if (str.length === 0) { - return str - } - return str.charAt(0).toUpperCase() + str.slice(1) -} diff --git a/tests/component/sections/dataset/dataset-files/files-table/files-info-cell/file-thumbnail/FileThumbnail.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/files-info-cell/file-thumbnail/FileThumbnail.spec.tsx index a14d5ef9a..167f10a21 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/files-info-cell/file-thumbnail/FileThumbnail.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/files-info-cell/file-thumbnail/FileThumbnail.spec.tsx @@ -20,7 +20,7 @@ describe('FileThumbnail', () => { it('renders FileThumbnailPreviewImage when thumbnail is provided', () => { const file = FileMother.create({ access: { restricted: false, canDownload: true }, - thumbnail: 'thumbnail' + thumbnail: 'thumbnail?' }) cy.customMount(