From 4e80cf893570b69b903c18df1c2a1073c69f40dc Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 31 Oct 2023 12:13:27 +0100 Subject: [PATCH 01/58] fix(FilesTable): incorrect behaviour when changing page size --- dev-env/.env | 2 +- src/files/domain/models/FilePaginationInfo.ts | 2 +- .../dataset-files/DatasetFiles.spec.tsx | 77 ++++++++++++++++++- .../dataset/dataset-files/useFiles.spec.tsx | 2 +- 4 files changed, 79 insertions(+), 4 deletions(-) diff --git a/dev-env/.env b/dev-env/.env index a7a8a8574..05d4bebe8 100644 --- a/dev-env/.env +++ b/dev-env/.env @@ -1,4 +1,4 @@ POSTGRES_VERSION=13 DATAVERSE_DB_USER=dataverse SOLR_VERSION=9.3.0 -REGISTRY=ghcr.io +REGISTRY=docker.io diff --git a/src/files/domain/models/FilePaginationInfo.ts b/src/files/domain/models/FilePaginationInfo.ts index 5f0e1ddcc..fbef8d177 100644 --- a/src/files/domain/models/FilePaginationInfo.ts +++ b/src/files/domain/models/FilePaginationInfo.ts @@ -24,7 +24,7 @@ export class FilePaginationInfo { withPageSize(pageSize: number): FilePaginationInfo { const getNewPage = (oldPageSize: number, newPageSize: number) => { - const newPage = Math.ceil((this.page * oldPageSize) / newPageSize) + const newPage = Math.ceil(((this.page - 1) * oldPageSize + 1) / newPageSize) return newPage > 0 ? newPage : 1 } return new FilePaginationInfo(getNewPage(this.pageSize, pageSize), pageSize, this.totalFiles) diff --git a/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx b/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx index 1e394acc6..3bb86c834 100644 --- a/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx @@ -98,7 +98,7 @@ describe('DatasetFiles', () => { cy.findByLabelText('Files per page').select('25') - cy.findByRole('columnheader', { name: '26 to 50 of 200 Files' }).should('exist') + cy.findByRole('columnheader', { name: '1 to 25 of 200 Files' }).should('exist') }) it('renders the files table with the correct header with a different page size ', () => { @@ -116,6 +116,81 @@ describe('DatasetFiles', () => { cy.findByRole('columnheader', { name: '101 to 150 of 200 Files' }).should('exist') }) + it('renders the first page if there is only one page and the user changes to a lower page size', () => { + const testFilesCountInfo = FilesCountInfoMother.create({ + total: 32 + }) + fileRepository.getAllByDatasetPersistentId = cy.stub().resolves(testFiles) + fileRepository.getFilesCountInfoByDatasetPersistentId = cy.stub().resolves(testFilesCountInfo) + fileRepository.getFilesTotalDownloadSizeByDatasetPersistentId = cy.stub().resolves(19900) + + cy.customMount( + + ) + + cy.findByRole('button', { name: '1' }).should('not.exist') + cy.findByRole('button', { name: '2' }).should('exist') + cy.findByRole('button', { name: '3' }).should('exist') + cy.findByRole('button', { name: '4' }).should('exist') + + cy.findByLabelText('Files per page').select('50') + + cy.findByRole('button', { name: '1' }).should('not.exist') + cy.findByRole('button', { name: '2' }).should('not.exist') + cy.findByRole('button', { name: '3' }).should('not.exist') + cy.findByRole('columnheader', { name: '1 to 32 of 32 Files' }).should('exist') + + cy.findByLabelText('Files per page').select('10') + + cy.findByRole('button', { name: '1' }).should('not.exist') + cy.findByRole('button', { name: '2' }).should('exist') + cy.findByRole('button', { name: '3' }).should('exist') + cy.findByRole('button', { name: '4' }).should('exist') + cy.findByRole('columnheader', { name: '1 to 10 of 32 Files' }).should('exist') + }) + + it('renders the page that includes the first element of the current page when changing the page size', () => { + const testFilesCountInfo = FilesCountInfoMother.create({ + total: 32 + }) + fileRepository.getAllByDatasetPersistentId = cy.stub().resolves(testFiles) + fileRepository.getFilesCountInfoByDatasetPersistentId = cy.stub().resolves(testFilesCountInfo) + fileRepository.getFilesTotalDownloadSizeByDatasetPersistentId = cy.stub().resolves(19900) + + cy.customMount( + + ) + + cy.findByRole('button', { name: '1' }).should('not.exist') + cy.findByRole('button', { name: '2' }).should('exist') + cy.findByRole('button', { name: '3' }).should('exist') + cy.findByRole('button', { name: '4' }).should('exist') + + cy.findByLabelText('Files per page').select('25') + + cy.findByRole('button', { name: '1' }).should('not.exist') + cy.findByRole('button', { name: '2' }).should('exist') + cy.findByRole('button', { name: '3' }).should('not.exist') + cy.findByRole('columnheader', { name: '1 to 25 of 32 Files' }).should('exist') + + cy.findByRole('button', { name: '2' }).click() + cy.findByLabelText('Files per page').select('10') + + cy.findByRole('button', { name: '1' }).should('exist') + cy.findByRole('button', { name: '2' }).should('exist') + cy.findByRole('button', { name: '3' }).should('not.exist') + cy.findByRole('button', { name: '4' }).should('exist') + cy.findByRole('columnheader', { name: '21 to 30 of 32 Files' }).should('exist') + }) + it('maintains the selection when the page changes', () => { cy.customMount( { cy.wrap(fileRepository.getAllByDatasetPersistentId).should('be.calledOnceWith', 'persistentId') cy.findByText('Loading...').should('exist') - cy.wrap(fileRepository.getUserPermissionsById).should('be.calledWith', files[0].id) + cy.wrap(fileRepository.getUserPermissionsById).should('be.called') cy.findByText('Loading...').should('exist') cy.findByText('Files count: 100').should('exist') From d3eb838851438078239f083ac537c61235944bb0 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Thu, 2 Nov 2023 10:06:59 +0100 Subject: [PATCH 02/58] feat(IntegrationDatasetPermissions): upgrade js-dataverse version --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index aef2e8157..170dd6d0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-pr93.8996380", + "@iqss/dataverse-client-javascript": "2.0.0-pr98.f3ad648", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -3588,9 +3588,9 @@ }, "node_modules/@iqss/dataverse-client-javascript": { "name": "@IQSS/dataverse-client-javascript", - "version": "2.0.0-pr93.8996380", - "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr93.8996380/e2b872620f16a08f5cb3e94dbf7afd0afc55e1d2", - "integrity": "sha512-hlSKtjPhpASVZD5RQsk2sRTrLWPbucY7G7uDcjWIWpg0NiHxhvz3Tny5q8yIAzQ44v+8K/n5K0fr8gv9PUBeiQ==", + "version": "2.0.0-pr98.f3ad648", + "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr98.f3ad648/7d7f41216e9f534a2382f4ed27534c4b33819af1", + "integrity": "sha512-Okb8Q9aEG+byT2XT1DrRCQsg9f0Si/zWkWLE6FWqQw2pUc/H7gQAfAuoyoYEAHKVTylJKHBFUH5HHP2BhXAlpA==", "license": "MIT", "dependencies": { "@types/node": "^18.15.11", diff --git a/package.json b/package.json index ca0daa72c..426658d6e 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-pr93.8996380", + "@iqss/dataverse-client-javascript": "2.0.0-pr98.f3ad648", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", From 7b14fa06a3ed70e7195766c6a8fb93e406b0a9c5 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Thu, 2 Nov 2023 12:17:29 +0100 Subject: [PATCH 03/58] feat(IntegrationDatasetPermissions): integrate canEditDataset permission --- dev-env/.env | 2 +- .../infrastructure/mappers/JSDatasetMapper.ts | 33 ++++++++++++------- .../DatasetJSDataverseRepository.ts | 28 ++++++++++++---- .../e2e/sections/dataset/Dataset.spec.tsx | 25 +++++++++++++- 4 files changed, 69 insertions(+), 19 deletions(-) diff --git a/dev-env/.env b/dev-env/.env index a7a8a8574..05d4bebe8 100644 --- a/dev-env/.env +++ b/dev-env/.env @@ -1,4 +1,4 @@ POSTGRES_VERSION=13 DATAVERSE_DB_USER=dataverse SOLR_VERSION=9.3.0 -REGISTRY=ghcr.io +REGISTRY=docker.io diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index ce3ade4b9..cef9e67a4 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -3,7 +3,8 @@ import { DatasetMetadataBlock as JSDatasetMetadataBlock, DatasetMetadataBlocks as JSDatasetMetadataBlocks, DatasetMetadataFields as JSDatasetMetadataFields, - DatasetVersionInfo as JSDatasetVersionInfo + DatasetVersionInfo as JSDatasetVersionInfo, + DatasetUserPermissions as JSDatasetPermissions } from '@iqss/dataverse-client-javascript' import { DatasetVersionState as JSDatasetVersionState } from '@iqss/dataverse-client-javascript/dist/datasets/domain/models/Dataset' import { @@ -13,11 +14,17 @@ import { DatasetMetadataBlocks, DatasetMetadataFields, DatasetVersion, - MetadataBlockName + MetadataBlockName, + DatasetPermissions } from '../../domain/models/Dataset' export class JSDatasetMapper { - static toDataset(jsDataset: JSDataset, citation: string, summaryFieldsNames: string[]): Dataset { + static toDataset( + jsDataset: JSDataset, + citation: string, + summaryFieldsNames: string[], + jsDatasetPermissions: JSDatasetPermissions + ): Dataset { return new Dataset.Builder( jsDataset.persistentId, JSDatasetMapper.toVersion(jsDataset.versionId, jsDataset.versionInfo), @@ -30,14 +37,7 @@ export class JSDatasetMapper { jsDataset.publicationDate, jsDataset.citationDate ), - { - canDownloadFiles: true, - canUpdateDataset: true, - canPublishDataset: true, - canManageDatasetPermissions: true, - canManageFilesPermissions: true, - canDeleteDataset: true - }, // TODO Connect with dataset permissions + JSDatasetMapper.toDatasetPermissions(jsDatasetPermissions), [], // TODO Connect with dataset locks true, // TODO Connect with dataset hasValidTermsOfAccess true, // TODO Connect with dataset isValid @@ -173,4 +173,15 @@ export class JSDatasetMapper { return extraFields } + + static toDatasetPermissions(jsDatasetPermissions: JSDatasetPermissions): DatasetPermissions { + return { + canDownloadFiles: true, + canUpdateDataset: jsDatasetPermissions.canEditDataset, + canPublishDataset: true, + canManageDatasetPermissions: true, + canManageFilesPermissions: true, + canDeleteDataset: true + } + } } diff --git a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts index ea25c0aa2..0f0dbc28f 100644 --- a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts +++ b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts @@ -6,8 +6,10 @@ import { getDatasetSummaryFieldNames, WriteError, Dataset as JSDataset, + DatasetUserPermissions as JSDatasetPermissions, getPrivateUrlDataset, - getPrivateUrlDatasetCitation + getPrivateUrlDatasetCitation, + getDatasetUserPermissions } from '@iqss/dataverse-client-javascript' import { JSDatasetMapper } from '../mappers/JSDatasetMapper' @@ -19,11 +21,18 @@ export class DatasetJSDataverseRepository implements DatasetRepository { Promise.all([ jsDataset, getDatasetSummaryFieldNames.execute(), - getDatasetCitation.execute(jsDataset.id, this.versionToVersionId(version)) + getDatasetCitation.execute(jsDataset.id, this.versionToVersionId(version)), + getDatasetUserPermissions.execute(jsDataset.id) ]) ) - .then(([jsDataset, summaryFieldsNames, citation]: [JSDataset, string[], string]) => - JSDatasetMapper.toDataset(jsDataset, citation, summaryFieldsNames) + .then( + ([jsDataset, summaryFieldsNames, citation, jsDatasetPermissions]: [ + JSDataset, + string[], + string, + JSDatasetPermissions + ]) => + JSDatasetMapper.toDataset(jsDataset, citation, summaryFieldsNames, jsDatasetPermissions) ) .catch((error: WriteError) => { if (!version) { @@ -39,8 +48,15 @@ export class DatasetJSDataverseRepository implements DatasetRepository { getDatasetSummaryFieldNames.execute(), getPrivateUrlDatasetCitation.execute(privateUrlToken) ]) - .then(([jsDataset, summaryFieldsNames, citation]: [JSDataset, string[], string]) => - JSDatasetMapper.toDataset(jsDataset, citation, summaryFieldsNames) + .then( + ([jsDataset, summaryFieldsNames, citation]: [JSDataset, string[], string]) => + JSDatasetMapper.toDataset(jsDataset, citation, summaryFieldsNames, { + canEditDataset: true, + canPublishDataset: true, + canManageDatasetPermissions: true, + canDeleteDatasetDraft: true, + canViewUnpublishedDataset: true + }) // TODO Connect with JS dataset permissions ) .catch((error: WriteError) => { throw new Error(error.message) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 4973b2165..d32ea2ed2 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -30,6 +30,29 @@ describe('Dataset', () => { cy.findByText(DatasetLabelValue.DRAFT).should('exist') // cy.findByText(DatasetLabelValue.UNPUBLISHED).should('exist') TODO - Implemnent isReleased property in js-dataverse to get the Unpublished label + cy.findByRole('button', { name: 'Edit Dataset' }).should('exist') + cy.findByRole('button', { name: 'Upload Files' }).should('exist') + cy.findByText('Metadata').should('exist') + cy.findByText('Files').should('exist') + }) + }) + }) + + it('successfully loads a published dataset when the user is not authenticated', () => { + cy.wrap(DatasetHelper.create().then((dataset) => DatasetHelper.publish(dataset.persistentId))) + .its('persistentId') + .then((persistentId: string) => { + cy.wrap(TestsUtils.logout()) + cy.wait(1500) // Wait for the dataset to be published + cy.visit(`/spa/datasets?persistentId=${persistentId}`) + + cy.fixture('dataset-finch1.json').then((dataset: Dataset) => { + cy.findByRole('heading', { + name: dataset.datasetVersion.metadataBlocks.citation.fields[0].value + }).should('exist') + + cy.findByRole('button', { name: 'Edit Dataset' }).should('not.exist') + cy.findByRole('button', { name: 'Upload Files' }).should('not.exist') cy.findByText('Metadata').should('exist') cy.findByText('Files').should('exist') }) @@ -51,7 +74,7 @@ describe('Dataset', () => { cy.wrap(DatasetHelper.create().then((dataset) => DatasetHelper.publish(dataset.persistentId))) .its('persistentId') .then((persistentId: string) => { - cy.wait(1500) + cy.wait(1500) // Wait for the dataset to be published cy.visit(`/spa/datasets?persistentId=${persistentId}&version=1.0`) cy.fixture('dataset-finch1.json').then((dataset: Dataset) => { From cdc9de2c2ae114665716f813915b9ba82865ab6e Mon Sep 17 00:00:00 2001 From: MellyGray Date: Fri, 3 Nov 2023 11:26:57 +0100 Subject: [PATCH 04/58] feat(IntegrationDatasetPermissions): add permissions to DatasetRepository integration test --- dev.Dockerfile | 1 + package-lock.json | 8 +++++--- package.json | 5 ++++- .../e2e/sections/dataset/Dataset.spec.tsx | 1 - .../datasets/DatasetJSDataverseRepository.spec.ts | 11 +++++++++++ 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/dev.Dockerfile b/dev.Dockerfile index 5e474faf8..fb0b8ee2e 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -10,6 +10,7 @@ RUN npm run build WORKDIR /usr/src/app COPY package.json ./ +COPY package-lock.json ./ COPY .npmrc ./ RUN npm install diff --git a/package-lock.json b/package-lock.json index 170dd6d0f..2abed2bfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5913,12 +5913,14 @@ } }, "node_modules/@parcel/watcher": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz", - "integrity": "sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.1.0.tgz", + "integrity": "sha512-8s8yYjd19pDSsBpbkOHnT6Z2+UJSuLQx61pCFM0s5wSRvKCEMDjd/cHY3/GI1szHIWbpXpsJdg3V6ISGGx9xDw==", "dev": true, "hasInstallScript": true, "dependencies": { + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", "node-addon-api": "^3.2.1", "node-gyp-build": "^4.3.0" }, diff --git a/package.json b/package.json index 426658d6e..450ef5b8d 100644 --- a/package.json +++ b/package.json @@ -164,5 +164,8 @@ "workerDirectory": "public" }, "readme": "ERROR: No README data found!", - "_id": "dataverse-frontend@0.1.0" + "_id": "dataverse-frontend@0.1.0", + "overrides": { + "@parcel/watcher": "2.1.0" + } } diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index d32ea2ed2..f57ae4d7f 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -31,7 +31,6 @@ describe('Dataset', () => { // cy.findByText(DatasetLabelValue.UNPUBLISHED).should('exist') TODO - Implemnent isReleased property in js-dataverse to get the Unpublished label cy.findByRole('button', { name: 'Edit Dataset' }).should('exist') - cy.findByRole('button', { name: 'Upload Files' }).should('exist') cy.findByText('Metadata').should('exist') cy.findByText('Files').should('exist') }) diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index 480456aeb..3ddac7fb8 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -79,6 +79,14 @@ const datasetData = (persistentId: string, versionId: number) => { latestVersionStatus: 'draft', isLatest: true, isInReview: false + }, + permissions: { + canDownloadFiles: true, + canUpdateDataset: true, + canPublishDataset: true, + canManageDatasetPermissions: true, + canManageFilesPermissions: true, + canDeleteDataset: true } } } @@ -106,6 +114,7 @@ describe('Dataset JSDataverse Repository', () => { expect(dataset.version).to.deep.equal(datasetExpected.version) expect(dataset.metadataBlocks[0].fields.publicationDate).not.to.exist expect(dataset.metadataBlocks[0].fields.citationDate).not.to.exist + expect(dataset.permissions).to.deep.equal(datasetExpected.permissions) }) }) @@ -138,6 +147,7 @@ describe('Dataset JSDataverse Repository', () => { expectedPublicationDate ) expect(dataset.metadataBlocks[0].fields.citationDate).not.to.exist + expect(dataset.permissions).to.deep.equal(datasetExpected.permissions) }) }) @@ -169,6 +179,7 @@ describe('Dataset JSDataverse Repository', () => { expect(dataset.getTitle()).to.deep.equal(datasetExpected.title) expect(dataset.version).to.deep.equal(datasetExpected.version) + expect(dataset.permissions).to.deep.equal(datasetExpected.permissions) }) }) From 5ca615cf90caf2f9cd7390581972065405f6da07 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Fri, 3 Nov 2023 15:34:32 +0100 Subject: [PATCH 05/58] feat(IntegrationDatasetPermissions): integrate canPublishDataset permission --- src/dataset/infrastructure/mappers/JSDatasetMapper.ts | 2 +- tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index a6a6503d4..5f548bd20 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -186,7 +186,7 @@ export class JSDatasetMapper { return { canDownloadFiles: true, canUpdateDataset: jsDatasetPermissions.canEditDataset, - canPublishDataset: true, + canPublishDataset: jsDatasetPermissions.canPublishDataset, canManageDatasetPermissions: true, canManageFilesPermissions: true, canDeleteDataset: true diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 71f4003d2..6ea032709 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -31,6 +31,8 @@ describe('Dataset', () => { // cy.findByText(DatasetLabelValue.UNPUBLISHED).should('exist') TODO - Implemnent isReleased property in js-dataverse to get the Unpublished label cy.findByRole('button', { name: 'Edit Dataset' }).should('exist') + cy.findByRole('button', { name: 'Publish Dataset' }).should('exist') + cy.findByText('Metadata').should('exist') cy.findByText('Files').should('exist') }) @@ -51,6 +53,7 @@ describe('Dataset', () => { }).should('exist') cy.findByRole('button', { name: 'Edit Dataset' }).should('not.exist') + cy.findByRole('button', { name: 'Publish Dataset' }).should('not.exist') cy.findByRole('button', { name: 'Upload Files' }).should('not.exist') cy.findByText('Metadata').should('exist') cy.findByText('Files').should('exist') From b48973c8b18a3e97c44cdb4e5538f89ca3bc025c Mon Sep 17 00:00:00 2001 From: MellyGray Date: Fri, 3 Nov 2023 15:55:02 +0100 Subject: [PATCH 06/58] feat(IntegrationDatasetPermissions): add manageDatasetPermissions permission integration --- .../infrastructure/mappers/JSDatasetMapper.ts | 2 +- .../SettingJSDataverseRepository.ts | 30 +++++++++++-- .../e2e/sections/dataset/Dataset.spec.tsx | 8 ++-- .../DatasetJSDataverseRepository.spec.ts | 42 +++++++++++++++++++ 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index 5f548bd20..a9790bbc7 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -187,7 +187,7 @@ export class JSDatasetMapper { canDownloadFiles: true, canUpdateDataset: jsDatasetPermissions.canEditDataset, canPublishDataset: jsDatasetPermissions.canPublishDataset, - canManageDatasetPermissions: true, + canManageDatasetPermissions: jsDatasetPermissions.canManageDatasetPermissions, canManageFilesPermissions: true, canDeleteDataset: true } diff --git a/src/settings/infrastructure/SettingJSDataverseRepository.ts b/src/settings/infrastructure/SettingJSDataverseRepository.ts index eb5801de7..e55e995fe 100644 --- a/src/settings/infrastructure/SettingJSDataverseRepository.ts +++ b/src/settings/infrastructure/SettingJSDataverseRepository.ts @@ -9,11 +9,33 @@ export class SettingJSDataverseRepository implements SettingRepository { // TODO - implement using js-dataverse return new Promise((resolve) => { setTimeout(() => { - resolve({ - name: SettingName.ZIP_DOWNLOAD_LIMIT, - value: new ZipDownloadLimit(1, FileSizeUnit.BYTES) - } as Setting) + resolve(mockedSettingResponse(name)) }, 1000) }) } } + +function mockedSettingResponse(name: SettingName): Setting { + switch (name) { + case SettingName.ZIP_DOWNLOAD_LIMIT: + return { + name: SettingName.ZIP_DOWNLOAD_LIMIT, + value: new ZipDownloadLimit(1, FileSizeUnit.BYTES) + } as Setting + case SettingName.ALLOWED_EXTERNAL_STATUSES: + return { + name: SettingName.ALLOWED_EXTERNAL_STATUSES, + value: [ + 'Author Contacted', + 'Privacy Review', + 'Awaiting Paper Publication', + 'Final Approval' + ] + } as Setting + case SettingName.HAS_PUBLIC_STORE: + return { + name: SettingName.HAS_PUBLIC_STORE, + value: false + } as Setting + } +} diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 6ea032709..b5e242984 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -30,11 +30,13 @@ describe('Dataset', () => { cy.findByText(DatasetLabelValue.DRAFT).should('exist') // cy.findByText(DatasetLabelValue.UNPUBLISHED).should('exist') TODO - Implemnent isReleased property in js-dataverse to get the Unpublished label - cy.findByRole('button', { name: 'Edit Dataset' }).should('exist') - cy.findByRole('button', { name: 'Publish Dataset' }).should('exist') - cy.findByText('Metadata').should('exist') cy.findByText('Files').should('exist') + + cy.findByRole('button', { name: 'Edit Dataset' }).should('exist').click() + cy.findByRole('button', { name: 'Permissions' }).should('exist').click() + cy.findByRole('button', { name: 'Dataset' }).should('exist').click() + cy.findByRole('button', { name: 'Publish Dataset' }).should('exist') }) }) }) diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index 7cd736ee9..52dae513f 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -119,6 +119,48 @@ describe('Dataset JSDataverse Repository', () => { }) }) + it('gets a published dataset by persistentId without user authentication', async () => { + const datasetResponse = await DatasetHelper.create() + await DatasetHelper.publish(datasetResponse.persistentId) + + await TestsUtils.wait(1500) + + await TestsUtils.logout() + + await datasetRepository + .getByPersistentId(datasetResponse.persistentId, '1.0') + .then((dataset) => { + if (!dataset) { + throw new Error('Dataset not found') + } + const datasetExpected = datasetData(dataset.persistentId, dataset.version.id) + const newVersion = new DatasetVersion( + dataset.version.id, + DatasetPublishingStatus.RELEASED, + true, + false, + DatasetPublishingStatus.RELEASED, + 1, + 0 + ) + const expectedPublicationDate = getCurrentDateInYYYYMMDDFormat() + expect(dataset.getTitle()).to.deep.equal(datasetExpected.title) + expect(dataset.version).to.deep.equal(newVersion) + expect(dataset.metadataBlocks[0].fields.publicationDate).to.deep.equal( + expectedPublicationDate + ) + expect(dataset.metadataBlocks[0].fields.citationDate).not.to.exist + expect(dataset.permissions).to.deep.equal({ + canDownloadFiles: true, + canUpdateDataset: false, + canPublishDataset: false, + canManageDatasetPermissions: false, + canManageFilesPermissions: true, + canDeleteDataset: true + }) + }) + }) + it('gets the dataset by persistentId and version number', async () => { const datasetResponse = await DatasetHelper.create() await DatasetHelper.publish(datasetResponse.persistentId) From 4e717f060fb90770a9d92973b3c1ba74baaf8c2d Mon Sep 17 00:00:00 2001 From: MellyGray Date: Fri, 3 Nov 2023 15:58:21 +0100 Subject: [PATCH 07/58] feat(IntegrationDatasetPermissions): add canDeleteDataset permission --- src/dataset/infrastructure/mappers/JSDatasetMapper.ts | 2 +- tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx | 1 + .../integration/datasets/DatasetJSDataverseRepository.spec.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index a9790bbc7..8652ced6a 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -189,7 +189,7 @@ export class JSDatasetMapper { canPublishDataset: jsDatasetPermissions.canPublishDataset, canManageDatasetPermissions: jsDatasetPermissions.canManageDatasetPermissions, canManageFilesPermissions: true, - canDeleteDataset: true + canDeleteDataset: jsDatasetPermissions.canManageDatasetPermissions } } } diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index b5e242984..91afa8491 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -36,6 +36,7 @@ describe('Dataset', () => { cy.findByRole('button', { name: 'Edit Dataset' }).should('exist').click() cy.findByRole('button', { name: 'Permissions' }).should('exist').click() cy.findByRole('button', { name: 'Dataset' }).should('exist').click() + cy.findByRole('button', { name: 'Delete Dataset' }).should('exist') cy.findByRole('button', { name: 'Publish Dataset' }).should('exist') }) }) diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index 52dae513f..a83e4b1e9 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -156,7 +156,7 @@ describe('Dataset JSDataverse Repository', () => { canPublishDataset: false, canManageDatasetPermissions: false, canManageFilesPermissions: true, - canDeleteDataset: true + canDeleteDataset: false }) }) }) From 3fd7fd9df3b96b6abe0a04777b78245fb3d1ff2e Mon Sep 17 00:00:00 2001 From: MellyGray Date: Fri, 3 Nov 2023 16:10:08 +0100 Subject: [PATCH 08/58] feat(IntegrationDatasetPermissions): add TODO canManageFilesPermissions permission --- src/dataset/infrastructure/mappers/JSDatasetMapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index 8652ced6a..7f4dbee89 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -188,7 +188,7 @@ export class JSDatasetMapper { canUpdateDataset: jsDatasetPermissions.canEditDataset, canPublishDataset: jsDatasetPermissions.canPublishDataset, canManageDatasetPermissions: jsDatasetPermissions.canManageDatasetPermissions, - canManageFilesPermissions: true, + canManageFilesPermissions: true, // TODO: connect with js-dataverse DatasetPermissions.canManageFilesPermissions canDeleteDataset: jsDatasetPermissions.canManageDatasetPermissions } } From 049d9939edab122666d278cdc1b300e325c4e21b Mon Sep 17 00:00:00 2001 From: MellyGray Date: Fri, 3 Nov 2023 17:18:56 +0100 Subject: [PATCH 09/58] feat(IntegrationDataset): add isRealeased property to JSDatasetMapper --- .../infrastructure/mappers/JSDatasetMapper.ts | 12 +++++++++--- .../e2e/sections/dataset/Dataset.spec.tsx | 10 +++++----- .../datasets/DatasetJSDataverseRepository.spec.ts | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index 7f4dbee89..4b4e49fb8 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -44,8 +44,7 @@ export class JSDatasetMapper { [], // TODO Connect with dataset locks true, // TODO Connect with dataset hasValidTermsOfAccess true, // TODO Connect with dataset isValid - jsDataset.versionInfo.releaseTime !== undefined && - !isNaN(jsDataset.versionInfo.releaseTime.getTime()), // TODO Connect with dataset isReleased, + JSDatasetMapper.toIsReleased(jsDataset.versionInfo), undefined, // TODO: get dataset thumbnail from Dataverse https://github.com/IQSS/dataverse-frontend/issues/203 privateUrl ).build() @@ -182,9 +181,16 @@ export class JSDatasetMapper { return extraFields } + static toIsReleased(jsDatasetVersionInfo: JSDatasetVersionInfo): boolean { + return ( + jsDatasetVersionInfo.releaseTime !== undefined && + !isNaN(jsDatasetVersionInfo.releaseTime.getTime()) + ) + } + static toDatasetPermissions(jsDatasetPermissions: JSDatasetPermissions): DatasetPermissions { return { - canDownloadFiles: true, + canDownloadFiles: true, // TODO: connect with js-dataverse canUpdateDataset: jsDatasetPermissions.canEditDataset, canPublishDataset: jsDatasetPermissions.canPublishDataset, canManageDatasetPermissions: jsDatasetPermissions.canManageDatasetPermissions, diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 91afa8491..f93c76e2a 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -28,14 +28,14 @@ describe('Dataset', () => { name: dataset.datasetVersion.metadataBlocks.citation.fields[0].value }).should('exist') cy.findByText(DatasetLabelValue.DRAFT).should('exist') - // cy.findByText(DatasetLabelValue.UNPUBLISHED).should('exist') TODO - Implemnent isReleased property in js-dataverse to get the Unpublished label + cy.findByText(DatasetLabelValue.UNPUBLISHED).should('exist') cy.findByText('Metadata').should('exist') cy.findByText('Files').should('exist') cy.findByRole('button', { name: 'Edit Dataset' }).should('exist').click() cy.findByRole('button', { name: 'Permissions' }).should('exist').click() - cy.findByRole('button', { name: 'Dataset' }).should('exist').click() + cy.findByRole('button', { name: 'Dataset' }).should('exist') cy.findByRole('button', { name: 'Delete Dataset' }).should('exist') cy.findByRole('button', { name: 'Publish Dataset' }).should('exist') }) @@ -87,7 +87,7 @@ describe('Dataset', () => { name: dataset.datasetVersion.metadataBlocks.citation.fields[0].value }).should('exist') cy.findByText(DatasetLabelValue.DRAFT).should('not.exist') - // cy.findByText(DatasetLabelValue.UNPUBLISHED).should('not.exist') TODO - Implemnent isReleased property in js-dataverse to get the Unpublished label + cy.findByText(DatasetLabelValue.UNPUBLISHED).should('not.exist') cy.findByText('Version 1.0').should('exist') }) }) @@ -127,7 +127,7 @@ describe('Dataset', () => { name: dataset.datasetVersion.metadataBlocks.citation.fields[0].value }).should('exist') cy.findByText(DatasetLabelValue.DRAFT).should('exist') - // cy.findByText(DatasetLabelValue.UNPUBLISHED).should('exist') TODO - Implemnent isReleased property in js-dataverse to get the Unpublished label + cy.findByText(DatasetLabelValue.UNPUBLISHED).should('exist') }) }) }) @@ -147,7 +147,7 @@ describe('Dataset', () => { name: dataset.datasetVersion.metadataBlocks.citation.fields[0].value }).should('exist') cy.findByText(DatasetLabelValue.DRAFT).should('exist') - // cy.findByText(DatasetLabelValue.UNPUBLISHED).should('exist') TODO - Implemnent isReleased property in js-dataverse to get the Unpublished label + cy.findByText(DatasetLabelValue.UNPUBLISHED).should('exist') cy.findAllByText('withheld').should('exist') }) diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index a83e4b1e9..9c367ecdb 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -108,7 +108,7 @@ describe('Dataset JSDataverse Repository', () => { expect(dataset.getTitle()).to.deep.equal(datasetExpected.title) expect(dataset.citation).to.deep.equal(datasetExpected.citation) - // expect(dataset.labels).to.deep.equal(datasetExpected.labels) TODO - Implemnent isReleased property in js-dataverse to get the Unpublished label + expect(dataset.labels).to.deep.equal(datasetExpected.labels) expect(dataset.license).to.deep.equal(datasetExpected.license) expect(dataset.metadataBlocks).to.deep.equal(datasetExpected.metadataBlocks) expect(dataset.summaryFields).to.deep.equal(datasetExpected.summaryFields) From 4c71c0475d77a33f5447e73fb819317b80017405 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 6 Nov 2023 10:27:07 +0100 Subject: [PATCH 10/58] feat(FilesTable): add friendly file type mapping --- src/files/domain/models/File.ts | 9 +- .../models/FileTypeToFriendlyTypeMap.ts | 227 ++++++++++++++++++ .../FileCriteriaFilterByType.tsx | 8 +- .../dataset-files/DatasetFiles.spec.tsx | 14 +- .../FileCriteriaFilterByTag.spec.tsx | 3 +- .../FileCriteriaFilterByType.spec.tsx | 41 ++-- .../FileCriteriaForm.spec.tsx | 34 +-- .../file-info-data/FileType.spec.tsx | 4 +- .../e2e/sections/dataset/Dataset.spec.tsx | 4 +- 9 files changed, 285 insertions(+), 59 deletions(-) create mode 100644 src/files/domain/models/FileTypeToFriendlyTypeMap.ts diff --git a/src/files/domain/models/File.ts b/src/files/domain/models/File.ts index d4255cda8..0be9f000c 100644 --- a/src/files/domain/models/File.ts +++ b/src/files/domain/models/File.ts @@ -1,3 +1,5 @@ +import FileTypeToFriendlyTypeMap from './FileTypeToFriendlyTypeMap' + export enum FileSizeUnit { BYTES = 'B', KILOBYTES = 'KB', @@ -122,12 +124,7 @@ 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(' ') + return FileTypeToFriendlyTypeMap[this.value] || this.value } } diff --git a/src/files/domain/models/FileTypeToFriendlyTypeMap.ts b/src/files/domain/models/FileTypeToFriendlyTypeMap.ts new file mode 100644 index 000000000..ab4d9b30e --- /dev/null +++ b/src/files/domain/models/FileTypeToFriendlyTypeMap.ts @@ -0,0 +1,227 @@ +const MimeTypeDisplay: Record = { + // Documentation + 'application/pdf': 'Adobe PDF', + 'image/pdf': 'Adobe PDF', + 'text/pdf': 'Adobe PDF', + 'application/x-pdf': 'Adobe PDF', + 'application/cnt': 'Windows Help Contents File', + 'application/msword': 'MS Word', + 'application/vnd.ms-excel': 'MS Excel Spreadsheet', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'MS Excel Spreadsheet', + 'application/vnd.ms-powerpoint': 'MS Powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'MS Powerpoint', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'MS Word', + 'application/vnd.oasis.opendocument.spreadsheet': 'OpenOffice Spreadsheet', + 'application/vnd.ms-excel.sheet.macroenabled.12': 'MS Excel Spreadsheet', + // Text + 'text/plain': 'Plain Text', + 'text/x-log': 'Application Log', + 'text/html': 'HTML', + 'application/x-tex': 'LaTeX', + 'text/x-tex': 'LaTeX', + 'text/markdown': 'Markdown Text', + 'text/x-markdown': 'Markdown Text', + 'text/x-r-markdown': 'R Markdown Text', + 'application/rtf': 'Rich Text Format', + 'text/x-rst': 'reStructuredText', + 'text/rtf': 'Rich Text Format', + 'text/richtext': 'Rich Text Format', + 'text/turtle': 'Turtle RDF', + 'application/xml': 'XML', + 'text/xml': 'XML', + // Code + 'text/x-c': 'C++ Source', + 'text/x-c++src': 'C++ Source', + 'text/css': 'Cascading Style Sheet', + 'text/x-fortran': 'Fortran Source Code', + 'application/java-vm': 'Java Class', + 'text/x-java-source': 'Java Source Code', + 'text/javascript': 'Javascript Code', + 'application/javascript': 'Javascript Code', + 'application/x-javascript': 'Javascript Code', + 'text/x-matlab': 'MATLAB Source Code', + 'text/x-mathematica': 'Mathematica Input', + 'text/x-objcsrc': 'Objective-C Source Code', + 'text/x-pascal': 'Pascal Source Code', + 'text/x-perl': 'Perl Script', + 'text/x-perl-script': 'Perl Script', + 'text/php': 'PHP Source Code', + 'application/postscript': 'Postscript', + 'text/x-python': 'Python Source Code', + 'text/x-python-script': 'Python Source Code', + 'text/x-r-source': 'R Source Code', + 'text/x-sh': 'Shell Script', + 'application/x-sh': 'Shell Script', + 'application/x-shellscript': 'Shell Script', + 'application/x-sql': 'SQL Code', + 'text/x-sql': 'SQL Code', + 'application/x-swc': 'Shockwave Flash Component', + 'application/x-msdownload': 'Windows Executable', + 'application/x-ipynb+json': 'Jupyter Notebook', + 'application/x-stata-ado': 'Stata Ado Script', + 'application/x-stata-do': 'Stata Do Script', + 'application/x-stata-dta': 'Stata Data Script', + 'application/x-stata-smcl': 'Stata Markup and Control Language', + 'text/x-stata-syntax': 'Stata Syntax', + 'application/x-stata-syntax': 'Stata Syntax', + 'text/x-spss-syntax': 'SPSS Syntax', + 'application/x-spss-syntax': 'SPSS Syntax', + 'application/x-spss-sps': 'SPSS Script Syntax', + 'text/x-sas-syntax': 'SAS Syntax', + 'application/x-sas-syntax': 'SAS Syntax', + 'type/x-r-syntax': 'R Syntax', + 'application/vnd.wolfram.mathematica.package': 'Wolfram Mathematica Code', + 'application/vnd.wolfram.mathematica': 'Wolfram Mathematica Code', + 'text/x-workflow-description-language': 'Workflow Description Language', + 'text/x-computational-workflow-language': 'Computational Workflow Language', + 'text/x-nextflow': 'Nextflow Script', + 'text/x-r-notebook': 'R Notebook', + 'text/x-ruby-script': 'Ruby Source Code', + 'text/x-dagman': 'DAGMan Workflow', + 'text/x-makefile': 'Makefile Script', + 'text/x-snakemake': 'Snakemake Workflow', + // Ingested Tabular Data + 'text/tab-separated-values': 'Tab-Delimited', + 'text/tsv': 'Tab-Separated Values', + 'text/comma-separated-values': 'Comma Separated Values', + 'text/x-comma-separated-values': 'Comma Separated Values', + 'text/csv': 'Comma Separated Values', + 'text/x-fixed-field': 'Fixed Field Text Data', + 'application/vnd.flographit': 'FloGraphIt Media', + 'application/x-r-data': 'R Data', + 'application/x-rlang-transport': 'R Data', + 'application/x-R-2': 'R Binary', + 'application/x-stata': 'Stata Binary', + 'application/x-stata-6': 'Stata Binary', + 'application/x-stata-13': 'Stata 13 Binary', + 'application/x-stata-14': 'Stata 14 Binary', + 'application/x-stata-15': 'Stata 15 Binary', + 'application/x-spss-por': 'SPSS Portable', + 'application/x-spss-portable': 'SPSS Portable', + 'application/x-spss-sav': 'SPSS Binary', + 'application/x-sas': 'SAS', + 'application/x-sas-transport': 'SAS Transport', + 'application/x-sas-system': 'SAS System', + 'application/x-sas-data': 'SAS Data', + 'application/x-sas-catalog': 'SAS Catalog', + 'application/x-sas-log': 'SAS Log', + 'application/x-sas-output': 'SAS Output', + 'application/softgrid-do': 'Softgrid DTA Script', + 'application/x-dvn-csvspss-zip': 'CSV (w/SPSS card)', + 'application/x-dvn-tabddi-zip': 'TAB (w/DDI)', + 'application/x-emf': 'Extended Metafile', + 'application/x-h5': 'Hierarchical Data Format', + 'application/x-hdf': 'Hierarchical Data Format', + 'application/x-hdf5': 'Hierarchical Data Format', + 'application/geo+json': 'GeoJSON', + 'application/json': 'JSON', + 'application/mathematica': 'Mathematica', + 'application/matlab-mat': 'MATLAB Data', + 'application/x-matlab-data': 'MATLAB Data', + 'application/x-matlab-figure': 'MATLAB Figure', + 'application/x-matlab-workspace': 'MATLAB Workspace', + 'text/x-vcard': 'Virtual Contact File', + 'application/x-xfig': 'MATLAB Figure', + 'application/x-msaccess': 'MS Access', + 'application/netcdf': 'Network Common Data Form', + 'application/x-netcdf': 'Network Common Data Form', + 'application/vnd.lotus-notes': 'Notes Storage Facility', + 'application/x-nsdstat': 'NSDstat', + 'application/vnd.realvnc.bed': 'PLINK Binary', + 'application/vnd.ms-pki.stl': 'STL Format', + 'application/vnd.isac.fcs': 'FCS Data', + 'application/java-serialized-object': 'Java Serialized Object', + 'chemical/x-xyz': 'Co-Ordinate Animation', + // FITS + 'image/fits': 'FITS', + 'application/fits': 'FITS', + // Shape + 'application/dbf': 'dBASE Table for ESRI Shapefile', + 'application/dbase': 'dBASE Table for ESRI Shapefile', + 'application/prj': 'ESRI Shapefile', + 'application/sbn': 'ESRI Spatial Index', + 'application/sbx': 'ESRI Spatial Index', + 'application/shp': 'Shape', + 'application/shx': 'Shape', + 'application/x-esri-shape': 'ESRI Shapefile', + 'application/vnd.google-earth.kml+xml': 'Keyhole Markup Language', + 'application/zipped-shapefile': 'Zipped Shapefiles', + // Archive + 'application/zip': 'ZIP Archive', + 'application/x-zip-compressed': 'ZIP Archive', + 'application/vnd.antix.game-component': 'ATX Archive', + 'application/x-bzip': 'Bzip Archive', + 'application/x-bzip2': 'Bzip Archive', + 'application/vnd.google-earth.kmz': 'Google Earth Archive', + 'application/gzip': 'Gzip Archive', + 'application/x-gzip': 'Gzip Archive', + 'application/x-gzip-compressed': 'Gzip Archive', + 'application/rar': 'RAR Archive', + 'application/x-rar': 'RAR Archive', + 'application/x-rar-compressed': 'RAR Archive', + 'application/tar': 'TAR Archive', + 'application/x-tar': 'TAR Archive', + 'application/x-compressed': 'Compressed Archive', + 'application/x-compressed-tar': 'TAR Archive', + 'application/x-7z-compressed': '7Z Archive', + 'application/x-xz': 'XZ Archive', + 'application/warc': 'Web Archive', + 'application/x-iso9660-image': 'Optical Disc Image', + 'application/vnd.eln+zip': 'ELN Archive', + // Image + 'image/gif': 'GIF Image', + 'image/jpeg': 'JPEG Image', + 'image/jp2': 'JPEG-2000 Image', + 'image/x-portable-bitmap': 'Bitmap Image', + 'image/x-portable-graymap': 'Graymap Image', + 'image/png': 'PNG Image', + 'image/x-portable-anymap': 'Anymap Image', + 'image/x-portable-pixmap': 'Pixmap Image', + 'application/x-msmetafile': 'Enhanced Metafile', + 'application/dicom': 'DICOM Image', + 'image/dicom-rle': 'DICOM Image', + 'image/nii': 'NIfTI Image', + 'image/cmu-raster': 'Raster Image', + 'image/x-rgb': 'RGB Image', + 'image/svg+xml': 'SVG Image', + 'image/tiff': 'TIFF Image', + 'image/bmp': 'Bitmap Image', + 'image/x-xbitmap': 'Bitmap Image', + 'image/RAW': 'Bitmap Image', + 'image/raw': 'Bitmap Image', + 'application/x-tgif': 'TGIF File', + 'image/x-xpixmap': 'Pixmap Image', + 'image/x-xwindowdump': 'X Windows Dump', + 'application/photoshop': 'Photoshop Image', + 'image/vnd.adobe.photoshop': 'Photoshop Image', + 'application/x-photoshop': 'Photoshop Image', + // Audio + 'audio/x-aiff': 'AIFF Audio', + 'audio/mp3': 'MP3 Audio', + 'audio/mpeg': 'MP3 Audio', + 'audio/mp4': 'MPEG-4 Audio', + 'audio/x-m4a': 'MPEG-4 Audio', + 'audio/ogg': 'OGG Audio', + 'audio/wav': 'Waveform Audio', + 'audio/x-wav': 'Waveform Audio', + 'audio/x-wave': 'Waveform Audio', + // Video + 'video/avi': 'AVI Video', + 'video/x-msvideo': 'AVI Video', + 'video/mpeg': 'MPEG Video', + 'video/mp4': 'MPEG-4 Video', + 'video/x-m4v': 'MPEG-4 Video', + 'video/ogg': 'OGG Video', + 'video/quicktime': 'Quicktime Video', + 'video/webm': 'WebM Video', + // Network Data + 'text/xml-graphml': 'GraphML Network Data', + // Other + 'application/octet-stream': 'Unknown', + 'application/x-docker-file': 'Docker Image File', + 'application/x-vagrant-file': 'Vagrant Image File', + // Dataverse-specific + 'application/vnd.dataverse.file-package': 'Dataverse Package' +} + +export default MimeTypeDisplay diff --git a/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByType.tsx b/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByType.tsx index 53f20b7f0..cf18e51da 100644 --- a/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByType.tsx +++ b/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByType.tsx @@ -23,13 +23,13 @@ export function FileCriteriaFilterByType({ }: FileCriteriaFilterByTypeProps) { const { t } = useTranslation('files') const [selectedType, setSelectedType] = useState( - criteria.filterByType ?? new FileType('all') + criteria.filterByType ?? new FileType('All') ) const handleTypeChange = (eventKey: string | null) => { if (selectedType.value !== eventKey) { setSelectedType(new FileType(eventKey as string)) onCriteriaChange( - criteria.withFilterByType(eventKey === 'all' ? undefined : (eventKey as string)) + criteria.withFilterByType(eventKey === 'All' ? undefined : (eventKey as string)) ) } } @@ -46,8 +46,8 @@ export function FileCriteriaFilterByType({ withSpacing variant="secondary"> + eventKey="All" + className={selectedType.value === 'All' ? styles['selected-option'] : ''}> All diff --git a/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx b/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx index f6ac50b7c..32cd4f0fe 100644 --- a/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx @@ -25,11 +25,11 @@ const testFilesCountInfo = FilesCountInfoMother.create({ total: 200, perFileType: [ { - type: new FileType('text'), + type: new FileType('text/plain'), count: 5 }, { - type: new FileType('image'), + type: new FileType('image/png'), count: 485 } ], @@ -188,7 +188,7 @@ describe('DatasetFiles', () => { cy.findByText('1 file is currently selected.').should('exist') cy.findByRole('button', { name: 'File Type: All' }).click() - cy.findByText('Image (485)').should('exist').click() + cy.findByText('PNG Image (485)').should('exist').click() cy.findByText('1 file is currently selected.').should('not.exist') }) @@ -357,13 +357,13 @@ describe('DatasetFiles', () => { ) cy.findByRole('button', { name: 'File Type: All' }).click() - cy.findByText('Image (485)').should('exist').click() + cy.findByText('PNG Image (485)').should('exist').click() cy.wrap(fileRepository.getAllByDatasetPersistentId).should( 'be.calledWith', datasetPersistentId, datasetVersion, filePaginationInfo, - new FileCriteria().withFilterByType('image') + new FileCriteria().withFilterByType('image/png') ) }) @@ -456,7 +456,7 @@ describe('DatasetFiles', () => { ) cy.findByRole('button', { name: 'File Type: All' }).click() - cy.findByText('Image (485)').should('exist').click() + cy.findByText('PNG Image (485)').should('exist').click() cy.get('table > thead > tr > th > input[type=checkbox]').click() cy.findByRole('button', { name: 'Select all 200 files in this dataset.' }).click() cy.findByText( @@ -467,7 +467,7 @@ describe('DatasetFiles', () => { 'be.calledWith', datasetPersistentId, datasetVersion, - new FileCriteria().withFilterByType('image') + new FileCriteria().withFilterByType('image/png') ) }) }) diff --git a/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByTag.spec.tsx b/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByTag.spec.tsx index d2acad803..66c28929f 100644 --- a/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByTag.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByTag.spec.tsx @@ -1,6 +1,5 @@ import { FileCriteria, FileTag } from '../../../../../../src/files/domain/models/FileCriteria' import { FilesCountInfoMother } from '../../../../files/domain/models/FilesCountInfoMother' -import { FileType } from '../../../../../../src/files/domain/models/File' import styles from '../../../../../../src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.module.scss' import { FileCriteriaFilterByTag } from '../../../../../../src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByTag' @@ -12,7 +11,7 @@ const filesCountInfo = FilesCountInfoMother.create({ count: 5 }, { - tag: new FileType('data'), + tag: new FileTag('data'), count: 10 } ] diff --git a/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByType.spec.tsx b/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByType.spec.tsx index e6c72496f..20a5a4435 100644 --- a/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByType.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByType.spec.tsx @@ -8,11 +8,11 @@ const defaultCriteria = new FileCriteria() const filesCountInfo = FilesCountInfoMother.create({ perFileType: [ { - type: new FileType('image'), + type: new FileType('image/png'), count: 5 }, { - type: new FileType('text'), + type: new FileType('text/plain'), count: 10 } ] @@ -33,8 +33,8 @@ describe('FilesCriteriaFilterByType', () => { cy.findByRole('button', { name: 'File Type: All' }).click() cy.findByText('All').should('exist') - cy.findByText('Image (5)').should('exist') - cy.findByText('Text (10)').should('exist') + cy.findByText('PNG Image (5)').should('exist') + cy.findByText('Plain Text (10)').should('exist') }) it('calls onCriteriaChange with the selected filter by type value', () => { @@ -49,17 +49,20 @@ describe('FilesCriteriaFilterByType', () => { ) cy.findByRole('button', { name: 'File Type: All' }).click() - cy.findByText('Image (5)').click() - cy.wrap(onCriteriaChange).should('be.calledWith', defaultCriteria.withFilterByType('image')) - - cy.findByRole('button', { name: 'File Type: Image' }).click() - cy.findByText('Text (10)').click() - cy.wrap(onCriteriaChange).should('be.calledWith', defaultCriteria.withFilterByType('text')) + cy.findByText('PNG Image (5)').click() + cy.wrap(onCriteriaChange).should('be.calledWith', defaultCriteria.withFilterByType('image/png')) + + cy.findByRole('button', { name: 'File Type: PNG Image' }).click() + cy.findByText('Plain Text (10)').click() + cy.wrap(onCriteriaChange).should( + 'be.calledWith', + defaultCriteria.withFilterByType('text/plain') + ) }) it('shows the selected filter in the dropdown title', () => { const onCriteriaChange = cy.stub().as('onCriteriaChange') - const criteria = defaultCriteria.withFilterByType('image') + const criteria = defaultCriteria.withFilterByType('image/png') cy.customMount( { /> ) - cy.findByRole('button', { name: 'File Type: Image' }).click() + cy.findByRole('button', { name: 'File Type: PNG Image' }).click() cy.findByText('All').should('exist').click() cy.wrap(onCriteriaChange).should('be.calledWith', defaultCriteria.withFilterByType(undefined)) }) @@ -88,13 +91,13 @@ describe('FilesCriteriaFilterByType', () => { cy.findByRole('button', { name: 'File Type: All' }).click() cy.findByText('All').should('have.class', styles['selected-option']) - cy.findByRole('button', { name: 'Image (5)' }).click() - cy.findByRole('button', { name: 'File Type: Image' }).click() - cy.findByText('Image (5)').should('have.class', styles['selected-option']) + cy.findByRole('button', { name: 'PNG Image (5)' }).click() + cy.findByRole('button', { name: 'File Type: PNG Image' }).click() + cy.findByText('PNG Image (5)').should('have.class', styles['selected-option']) - cy.findByRole('button', { name: 'Text (10)' }).click() - cy.findByRole('button', { name: 'File Type: Text' }).click() - cy.findByText('Text (10)').should('have.class', styles['selected-option']) + cy.findByRole('button', { name: 'Plain Text (10)' }).click() + cy.findByRole('button', { name: 'File Type: Plain Text' }).click() + cy.findByText('Plain Text (10)').should('have.class', styles['selected-option']) }) it('does not render the filter by type dropdown if there are no filter options', () => { @@ -109,6 +112,6 @@ describe('FilesCriteriaFilterByType', () => { /> ) - cy.findByRole('button', { name: 'File Type: Image' }).should('not.exist') + cy.findByRole('button', { name: 'File Type: PNG Image' }).should('not.exist') }) }) diff --git a/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.spec.tsx b/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.spec.tsx index bd0fb0294..8322b7a71 100644 --- a/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.spec.tsx @@ -18,11 +18,11 @@ let onCriteriaChange = () => {} const filesCountInfo = FilesCountInfoMother.create({ perFileType: [ { - type: new FileType('image'), + type: new FileType('image/png'), count: 5 }, { - type: new FileType('text'), + type: new FileType('text/plain'), count: 10 } ], @@ -42,7 +42,7 @@ const filesCountInfo = FilesCountInfoMother.create({ count: 5 }, { - tag: new FileType('data'), + tag: new FileTag('data'), count: 10 } ] @@ -112,7 +112,7 @@ describe('FileCriteriaForm', () => { const criteria = new FileCriteria() .withFilterByTag('document') .withFilterByAccess(FileAccessOption.PUBLIC) - .withFilterByType('image') + .withFilterByType('image/png') .withSearchText('search text') cy.customMount( @@ -128,7 +128,7 @@ describe('FileCriteriaForm', () => { cy.wrap(onCriteriaChange).should('be.calledWith', criteria.withSortBy(FileSortByOption.OLDEST)) - cy.findByRole('button', { name: 'File Type: Image' }).should('exist') + cy.findByRole('button', { name: 'File Type: PNG Image' }).should('exist') cy.findByRole('button', { name: 'Access: Public' }).should('exist') cy.findByRole('button', { name: 'File Tags: Document' }).should('exist') cy.findByLabelText('Search').should('have.value', 'search text') @@ -139,7 +139,7 @@ describe('FileCriteriaForm', () => { const criteria = new FileCriteria() .withFilterByTag('document') .withFilterByAccess(FileAccessOption.PUBLIC) - .withFilterByType('image') + .withFilterByType('image/png') .withSearchText('search text') cy.customMount( @@ -150,12 +150,12 @@ describe('FileCriteriaForm', () => { /> ) - cy.findByRole('button', { name: 'File Type: Image' }).click() - cy.findByText('Text (10)').click() + cy.findByRole('button', { name: 'File Type: PNG Image' }).click() + cy.findByText('Plain Text (10)').click() - cy.wrap(onCriteriaChange).should('be.calledWith', criteria.withFilterByType('text')) + cy.wrap(onCriteriaChange).should('be.calledWith', criteria.withFilterByType('text/plain')) - cy.findByRole('button', { name: 'File Type: Text' }).should('exist') + cy.findByRole('button', { name: 'File Type: Plain Text' }).should('exist') cy.findByRole('button', { name: 'Access: Public' }).should('exist') cy.findByRole('button', { name: 'File Tags: Document' }).should('exist') cy.findByLabelText('Search').should('have.value', 'search text') @@ -166,7 +166,7 @@ describe('FileCriteriaForm', () => { const criteria = new FileCriteria() .withFilterByTag('document') .withFilterByAccess(FileAccessOption.PUBLIC) - .withFilterByType('image') + .withFilterByType('image/png') .withSearchText('search text') cy.customMount( @@ -185,7 +185,7 @@ describe('FileCriteriaForm', () => { criteria.withFilterByAccess(FileAccessOption.RESTRICTED) ) - cy.findByRole('button', { name: 'File Type: Image' }).should('exist') + cy.findByRole('button', { name: 'File Type: PNG Image' }).should('exist') cy.findByRole('button', { name: 'Access: Restricted' }).should('exist') cy.findByRole('button', { name: 'File Tags: Document' }).should('exist') cy.findByLabelText('Search').should('have.value', 'search text') @@ -196,7 +196,7 @@ describe('FileCriteriaForm', () => { const criteria = new FileCriteria() .withFilterByTag('document') .withFilterByAccess(FileAccessOption.PUBLIC) - .withFilterByType('image') + .withFilterByType('image/png') .withSearchText('search text') cy.customMount( @@ -212,7 +212,7 @@ describe('FileCriteriaForm', () => { cy.wrap(onCriteriaChange).should('be.calledWith', criteria.withFilterByTag('data')) - cy.findByRole('button', { name: 'File Type: Image' }).should('exist') + cy.findByRole('button', { name: 'File Type: PNG Image' }).should('exist') cy.findByRole('button', { name: 'Access: Public' }).should('exist') cy.findByRole('button', { name: 'File Tags: Data' }).should('exist') cy.findByLabelText('Search').should('have.value', 'search text') @@ -223,7 +223,7 @@ describe('FileCriteriaForm', () => { const criteria = new FileCriteria() .withFilterByTag('document') .withFilterByAccess(FileAccessOption.PUBLIC) - .withFilterByType('image') + .withFilterByType('image/png') .withSearchText('search text') cy.customMount( @@ -238,7 +238,7 @@ describe('FileCriteriaForm', () => { cy.wrap(onCriteriaChange).should('be.calledWith', criteria.withSearchText('new search')) - cy.findByRole('button', { name: 'File Type: Image' }).should('exist') + cy.findByRole('button', { name: 'File Type: PNG Image' }).should('exist') cy.findByRole('button', { name: 'Access: Public' }).should('exist') cy.findByRole('button', { name: 'File Tags: Document' }).should('exist') cy.findByLabelText('Search').should('have.value', 'new search') @@ -272,7 +272,7 @@ describe('FileCriteriaForm', () => { const criteria = new FileCriteria() .withFilterByTag('document') .withFilterByAccess(FileAccessOption.PUBLIC) - .withFilterByType('image') + .withFilterByType('image/png') cy.customMount( { }) cy.customMount() - cy.findByText(`Text/plain - 123 B`).should('exist') + cy.findByText(`Plain Text - 123 B`).should('exist') }) it('renders the type and size correctly when there are decimals', () => { @@ -24,6 +24,6 @@ describe('FileType', () => { }) cy.customMount() - cy.findByText(`Text/plain - 123.9 MB`).should('exist') + cy.findByText(`Plain Text - 123.9 MB`).should('exist') }) }) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index f93c76e2a..6e9efdb18 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -328,7 +328,7 @@ describe('Dataset', () => { }) }) - it('applies filters to the Files Table in the correct order', () => { + it.only('applies filters to the Files Table in the correct order', () => { const files = [ FileHelper.create('csv', { description: 'Some description', @@ -412,7 +412,7 @@ describe('Dataset', () => { cy.findByText('blob-5').should('exist') cy.findByRole('button', { name: 'File Type: All' }).click({ force: true }) - cy.findByText('Text/csv (2)').should('exist').click({ force: true }) + cy.findByText('Comma Separated Values (2)').should('exist').click({ force: true }) cy.findByText('1 to 2 of 2 Files').should('exist') cy.findByText('blob').should('not.exist') From d0125c08456f6049816a22a3dfbec7e5db2a8f49 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Thu, 9 Nov 2023 13:01:10 +0100 Subject: [PATCH 11/58] fix: display filters after the filter results in one file --- src/files/domain/models/FileCriteria.ts | 9 ++++++++ .../file-criteria-form/FileCriteriaForm.tsx | 5 ++++- .../FileCriteriaForm.spec.tsx | 19 +++++++++++++++++ .../e2e/sections/dataset/Dataset.spec.tsx | 21 ++++++++++++++++++- 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/files/domain/models/FileCriteria.ts b/src/files/domain/models/FileCriteria.ts index 8de6c9dab..30720deba 100644 --- a/src/files/domain/models/FileCriteria.ts +++ b/src/files/domain/models/FileCriteria.ts @@ -62,6 +62,15 @@ export class FileCriteria { searchText ) } + + get someFilterApplied(): boolean { + return ( + this.filterByType !== undefined || + this.filterByAccess !== undefined || + this.filterByTag !== undefined || + this.searchText !== undefined + ) + } } export enum FileSortByOption { diff --git a/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.tsx b/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.tsx index 5096d4001..abaad677e 100644 --- a/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.tsx +++ b/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.tsx @@ -18,7 +18,10 @@ export function FileCriteriaForm({ onCriteriaChange, filesCountInfo }: FileCriteriaInputsProps) { - if (!filesCountInfo || filesCountInfo.total < MINIMUM_FILES_TO_SHOW_CRITERIA_INPUTS) { + if ( + !filesCountInfo || + (filesCountInfo.total < MINIMUM_FILES_TO_SHOW_CRITERIA_INPUTS && !criteria.someFilterApplied) + ) { return <> } return ( diff --git a/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.spec.tsx b/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.spec.tsx index bd0fb0294..9da843687 100644 --- a/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.spec.tsx @@ -289,4 +289,23 @@ describe('FileCriteriaForm', () => { cy.findByLabelText('Search').should('have.value', 'test') }) + + it('renders the file criteria if there are less than 2 files but there is a filter applied', () => { + const criteria = new FileCriteria() + .withFilterByTag('document') + .withFilterByAccess(FileAccessOption.PUBLIC) + .withFilterByType('image') + + cy.customMount( + + ) + + cy.findByRole('button', { name: 'File Type: Image' }).should('exist') + cy.findByRole('button', { name: 'Access: Public' }).should('exist') + cy.findByRole('button', { name: 'File Tags: Document' }).should('exist') + }) }) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index ef743d439..b872564fa 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -300,7 +300,7 @@ describe('Dataset', () => { }) }) - it('applies filters to the Files Table in the correct order', () => { + it.only('applies filters to the Files Table in the correct order', () => { const files = [ FileHelper.create('csv', { description: 'Some description', @@ -403,6 +403,25 @@ describe('Dataset', () => { cy.findByText('blob-3').should('not.exist') cy.get('table > tbody > tr').eq(0).should('contain', 'blob-5') cy.get('table > tbody > tr').eq(1).should('contain', 'blob-4') + + cy.findByLabelText('Search').clear().type('blob-5{enter}', { force: true }) + + cy.findByText('1 to 1 of 1 Files').should('exist') + cy.findByText('blob').should('not.exist') + cy.findByText('blob-1').should('not.exist') + cy.findByText('blob-2').should('not.exist') + cy.findByText('blob-3').should('not.exist') + cy.findByText('blob-4').should('not.exist') + cy.findByText('blob-5').should('exist') + + cy.findByLabelText('Search').clear().type('{enter}', { force: true }) + cy.findByText('1 to 3 of 3 Files').should('exist') + cy.findByText('blob').should('exist') + cy.findByText('blob-1').should('not.exist') + cy.findByText('blob-2').should('not.exist') + cy.findByText('blob-3').should('not.exist') + cy.findByText('blob-4').should('exist') + cy.findByText('blob-5').should('exist') }) }) From 8c7b4c7fc4323b155ca2ed608a73acb78e83828c Mon Sep 17 00:00:00 2001 From: MellyGray Date: Fri, 10 Nov 2023 13:24:00 +0100 Subject: [PATCH 12/58] refactor: getFiles thumbnail and downloads information --- src/files/domain/models/File.ts | 4 +- .../FileJSDataverseRepository.ts | 52 +++++++------------ .../infrastructure/mappers/JSFileMapper.ts | 19 ++++--- .../files/FileJSDataverseRepository.spec.ts | 2 - 4 files changed, 34 insertions(+), 43 deletions(-) diff --git a/src/files/domain/models/File.ts b/src/files/domain/models/File.ts index d4255cda8..d31294ff8 100644 --- a/src/files/domain/models/File.ts +++ b/src/files/domain/models/File.ts @@ -157,12 +157,12 @@ export class File { readonly type: FileType, readonly size: FileSize, readonly date: FileDate, - public downloadCount: number, + readonly downloadCount: number, readonly labels: FileLabel[], public readonly isDeleted: boolean, public readonly ingest: FileIngest, readonly checksum?: FileChecksum, - public thumbnail?: string, + readonly thumbnail?: string, readonly directory?: string, readonly embargo?: FileEmbargo, readonly tabularData?: FileTabularData, diff --git a/src/files/infrastructure/FileJSDataverseRepository.ts b/src/files/infrastructure/FileJSDataverseRepository.ts index e16c3c004..cfb398322 100644 --- a/src/files/infrastructure/FileJSDataverseRepository.ts +++ b/src/files/infrastructure/FileJSDataverseRepository.ts @@ -1,5 +1,5 @@ import { FileRepository } from '../domain/repositories/FileRepository' -import { File, FilePublishingStatus } from '../domain/models/File' +import { File } from '../domain/models/File' import { FilesCountInfo } from '../domain/models/FilesCountInfo' import { FilePaginationInfo } from '../domain/models/FilePaginationInfo' import { FileUserPermissions } from '../domain/models/FileUserPermissions' @@ -10,7 +10,8 @@ import { getDatasetFilesTotalDownloadSize, getFileDownloadCount, getFileUserPermissions, - ReadError + ReadError, + File as JSFile } from '@iqss/dataverse-client-javascript' import { FileCriteria } from '../domain/models/FileCriteria' import { DomainFileMapper } from './mappers/DomainFileMapper' @@ -41,46 +42,33 @@ export class FileJSDataverseRepository implements FileRepository { DomainFileMapper.toJSFileSearchCriteria(criteria), DomainFileMapper.toJSFileOrderCriteria(criteria.sortBy) ) - .then((jsFiles) => jsFiles.map((jsFile) => JSFileMapper.toFile(jsFile, datasetVersion))) - .then((files) => FileJSDataverseRepository.getAllWithDownloadCount(files)) - .then((files) => FileJSDataverseRepository.getAllWithThumbnail(files)) + .then((jsFiles) => + Promise.all([ + jsFiles, + FileJSDataverseRepository.getAllDownloadCount(jsFiles), + FileJSDataverseRepository.getAllThumbnails(jsFiles) + ]) + ) + .then(([jsFiles, downloadCounts, thumbnails]) => + jsFiles.map((jsFile, index) => + JSFileMapper.toFile(jsFile, datasetVersion, downloadCounts[index], thumbnails[index]) + ) + ) .catch((error: ReadError) => { throw new Error(error.message) }) } - private static getAllWithDownloadCount(files: File[]): Promise { + private static getAllDownloadCount(jsFiles: JSFile[]): Promise { return Promise.all( - files.map((file) => - FileJSDataverseRepository.getDownloadCountById(file.id, file.version.publishingStatus).then( - (downloadCount) => { - file.downloadCount = downloadCount - return file - } - ) + jsFiles.map((jsFile) => + getFileDownloadCount.execute(jsFile.id).then((downloadCount) => Number(downloadCount)) ) ) } - private static getDownloadCountById( - id: number, - publishingStatus: FilePublishingStatus - ): Promise { - if (publishingStatus === FilePublishingStatus.RELEASED) { - return getFileDownloadCount.execute(id).then((downloadCount) => Number(downloadCount)) - } - return Promise.resolve(0) - } - - private static getAllWithThumbnail(files: File[]): Promise { - return Promise.all( - files.map((file) => - FileJSDataverseRepository.getThumbnailById(file.id).then((thumbnail) => { - file.thumbnail = thumbnail - return file - }) - ) - ) + private static getAllThumbnails(jsFiles: JSFile[]): Promise<(string | undefined)[]> { + return Promise.all(jsFiles.map((jsFile) => this.getThumbnailById(jsFile.id))) } private static getThumbnailById(id: number): Promise { diff --git a/src/files/infrastructure/mappers/JSFileMapper.ts b/src/files/infrastructure/mappers/JSFileMapper.ts index 316c5019c..1781427cf 100644 --- a/src/files/infrastructure/mappers/JSFileMapper.ts +++ b/src/files/infrastructure/mappers/JSFileMapper.ts @@ -35,7 +35,12 @@ import { import { FileAccessOption, FileTag } from '../../domain/models/FileCriteria' export class JSFileMapper { - static toFile(jsFile: JSFile, datasetVersion: DatasetVersion): File { + static toFile( + jsFile: JSFile, + datasetVersion: DatasetVersion, + downloadsCount: number, + thumbnail?: string + ): File { return new File( this.toFileId(jsFile.id), this.toFileVersion(jsFile.version, datasetVersion, jsFile.publicationDate), @@ -44,12 +49,12 @@ export class JSFileMapper { this.toFileType(jsFile.contentType), this.toFileSize(jsFile.sizeBytes), this.toFileDate(jsFile.creationDate, jsFile.publicationDate, jsFile.embargo), - this.toFileDownloads(), + this.toFileDownloads(downloadsCount), this.toFileLabels(jsFile.categories, jsFile.tabularTags), false, // TODO - Implement this when it is added to js-dataverse { status: FileIngestStatus.NONE }, // TODO - Implement this when it is added to js-dataverse this.toFileChecksum(jsFile.checksum), - this.toFileThumbnail(), + this.toFileThumbnail(thumbnail), this.toFileDirectory(jsFile.directoryLabel), this.toFileEmbargo(jsFile.embargo), this.toFileTabularData(), @@ -132,8 +137,8 @@ export class JSFileMapper { throw new Error('File date not found') } - static toFileDownloads(): number { - return 0 // This is always 0 because the downloads come from a different endpoint + static toFileDownloads(downloadsCount: number): number { + return downloadsCount } static toFileLabels(jsFileCategories?: string[], jsFileTabularTags?: string[]): FileLabel[] { @@ -159,8 +164,8 @@ export class JSFileMapper { return undefined } - static toFileThumbnail(): undefined { - return undefined // This is always undefined because the thumbnails come from a different endpoint + static toFileThumbnail(thumbnail?: string): string | undefined { + return thumbnail } static toFileDirectory(jsFileDirectory: string | undefined): string | undefined { diff --git a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts index d08c7bbd4..2fa91972c 100644 --- a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts @@ -264,7 +264,6 @@ describe('File JSDataverse Repository', () => { await fileRepository .getAllByDatasetPersistentId(dataset.persistentId, dataset.version) .then((files) => { - console.log(files) expect(files[0].thumbnail).to.not.be.undefined }) }) @@ -293,7 +292,6 @@ describe('File JSDataverse Repository', () => { }) it.skip('gets all the files by dataset persistentId when files are tabular data', async () => { - // TODO - Implement this when isTabularData flag is added to js-dataverse response const datasetResponse = await DatasetHelper.createWithFiles(FileHelper.createMany(1, 'csv')) if (!datasetResponse.files) throw new Error('Files not found') From 030eb0ffe1e6863135a9f2fce44443bf3a86eb0c Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 13 Nov 2023 10:26:49 +0100 Subject: [PATCH 13/58] feat(IntegrationTabularData): integrate js-dataverse tabular data --- src/files/domain/models/File.ts | 2 +- .../FileJSDataverseRepository.ts | 32 +++++++++++++++---- .../infrastructure/mappers/JSFileMapper.ts | 20 +++++++++--- .../file-info-data/FileTabularData.tsx | 2 +- .../DatasetCitation.stories.tsx | 1 - .../files/FileJSDataverseRepository.spec.ts | 15 ++++++--- 6 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/files/domain/models/File.ts b/src/files/domain/models/File.ts index d31294ff8..1e6fa2e3f 100644 --- a/src/files/domain/models/File.ts +++ b/src/files/domain/models/File.ts @@ -106,7 +106,7 @@ export class FileEmbargo { export interface FileTabularData { variablesCount: number observationsCount: number - unf: string + unf?: string } export enum FileLabelType { diff --git a/src/files/infrastructure/FileJSDataverseRepository.ts b/src/files/infrastructure/FileJSDataverseRepository.ts index cfb398322..e96c994bf 100644 --- a/src/files/infrastructure/FileJSDataverseRepository.ts +++ b/src/files/infrastructure/FileJSDataverseRepository.ts @@ -11,7 +11,9 @@ import { getFileDownloadCount, getFileUserPermissions, ReadError, - File as JSFile + File as JSFile, + getFileDataTables, + FileDataTable as JSFileTabularData } from '@iqss/dataverse-client-javascript' import { FileCriteria } from '../domain/models/FileCriteria' import { DomainFileMapper } from './mappers/DomainFileMapper' @@ -31,7 +33,6 @@ export class FileJSDataverseRepository implements FileRepository { criteria: FileCriteria = new FileCriteria() ): Promise { const jsPagination = DomainFileMapper.toJSPagination(paginationInfo) - return getDatasetFiles .execute( datasetPersistentId, @@ -46,12 +47,19 @@ export class FileJSDataverseRepository implements FileRepository { Promise.all([ jsFiles, FileJSDataverseRepository.getAllDownloadCount(jsFiles), - FileJSDataverseRepository.getAllThumbnails(jsFiles) + FileJSDataverseRepository.getAllThumbnails(jsFiles), + FileJSDataverseRepository.getAllTabularData(jsFiles) ]) ) - .then(([jsFiles, downloadCounts, thumbnails]) => + .then(([jsFiles, downloadCounts, thumbnails, jsTabularData]) => jsFiles.map((jsFile, index) => - JSFileMapper.toFile(jsFile, datasetVersion, downloadCounts[index], thumbnails[index]) + JSFileMapper.toFile( + jsFile, + datasetVersion, + downloadCounts[index], + thumbnails[index], + jsTabularData[index] + ) ) ) .catch((error: ReadError) => { @@ -59,10 +67,22 @@ export class FileJSDataverseRepository implements FileRepository { }) } + private static getAllTabularData( + jsFiles: JSFile[] + ): Promise<(JSFileTabularData[] | undefined)[]> { + return Promise.all( + jsFiles.map((jsFile) => + jsFile.tabularData ? getFileDataTables.execute(jsFile.id) : undefined + ) + ) + } + private static getAllDownloadCount(jsFiles: JSFile[]): Promise { return Promise.all( jsFiles.map((jsFile) => - getFileDownloadCount.execute(jsFile.id).then((downloadCount) => Number(downloadCount)) + jsFile.publicationDate + ? getFileDownloadCount.execute(jsFile.id).then((downloadCount) => Number(downloadCount)) + : 0 ) ) } diff --git a/src/files/infrastructure/mappers/JSFileMapper.ts b/src/files/infrastructure/mappers/JSFileMapper.ts index 1781427cf..7897ec315 100644 --- a/src/files/infrastructure/mappers/JSFileMapper.ts +++ b/src/files/infrastructure/mappers/JSFileMapper.ts @@ -11,6 +11,7 @@ import { FilePublishingStatus, FileSize, FileSizeUnit, + FileTabularData, FileType, FileVersion } from '../../domain/models/File' @@ -22,7 +23,8 @@ import { FileContentTypeCount as JSFileContentTypeCount, FileCategoryNameCount as JSFileCategoryNameCount, FileAccessStatusCount as JSFileAccessStatusCount, - FileAccessStatus as JSFileAccessStatus + FileAccessStatus as JSFileAccessStatus, + FileDataTable as JSFileTabularData } from '@iqss/dataverse-client-javascript' import { DatasetPublishingStatus, DatasetVersion } from '../../../dataset/domain/models/Dataset' import { FileUserPermissions } from '../../domain/models/FileUserPermissions' @@ -39,7 +41,8 @@ export class JSFileMapper { jsFile: JSFile, datasetVersion: DatasetVersion, downloadsCount: number, - thumbnail?: string + thumbnail?: string, + jsTabularData?: JSFileTabularData[] ): File { return new File( this.toFileId(jsFile.id), @@ -57,7 +60,7 @@ export class JSFileMapper { this.toFileThumbnail(thumbnail), this.toFileDirectory(jsFile.directoryLabel), this.toFileEmbargo(jsFile.embargo), - this.toFileTabularData(), + this.toFileTabularData(jsTabularData), this.toFileDescription(jsFile.description) ) } @@ -179,8 +182,15 @@ export class JSFileMapper { return undefined } - static toFileTabularData(): undefined { - return undefined // This is always undefined because the tabular data comes from a different endpoint + static toFileTabularData(jsTabularData?: JSFileTabularData[]): FileTabularData | undefined { + if (jsTabularData === undefined) { + return undefined + } + return { + variablesCount: jsTabularData[0].varQuantity ?? 0, + observationsCount: jsTabularData[0].caseQuantity ?? 0, + unf: jsTabularData[0].UNF + } } static toFileDescription(jsFileDescription?: string): string | undefined { diff --git a/src/sections/dataset/dataset-files/files-table/file-info/file-info-cell/file-info-data/FileTabularData.tsx b/src/sections/dataset/dataset-files/files-table/file-info/file-info-cell/file-info-data/FileTabularData.tsx index 27467ae59..21770da2e 100644 --- a/src/sections/dataset/dataset-files/files-table/file-info/file-info-cell/file-info-data/FileTabularData.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-info/file-info-cell/file-info-data/FileTabularData.tsx @@ -15,7 +15,7 @@ export function FileTabularData({
{tabularData.variablesCount} {t('table.tabularData.variables')},{' '} {tabularData.observationsCount} {t('table.tabularData.observations')}{' '} - + {tabularData.unf && }
) } diff --git a/src/stories/dataset/dataset-citation/DatasetCitation.stories.tsx b/src/stories/dataset/dataset-citation/DatasetCitation.stories.tsx index dc89d2f59..cd7d511d4 100644 --- a/src/stories/dataset/dataset-citation/DatasetCitation.stories.tsx +++ b/src/stories/dataset/dataset-citation/DatasetCitation.stories.tsx @@ -34,7 +34,6 @@ export const Default: Story = { export const WithThumbnail: Story = { render: () => { const dataset = DatasetMother.createRealistic({ thumbnail: faker.image.imageUrl() }) - console.log(dataset) return (


diff --git a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts index 2fa91972c..81d6b9540 100644 --- a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts @@ -291,7 +291,7 @@ describe('File JSDataverse Repository', () => { }) }) - it.skip('gets all the files by dataset persistentId when files are tabular data', async () => { + it('gets all the files by dataset persistentId when files are tabular data', async () => { const datasetResponse = await DatasetHelper.createWithFiles(FileHelper.createMany(1, 'csv')) if (!datasetResponse.files) throw new Error('Files not found') @@ -302,12 +302,17 @@ describe('File JSDataverse Repository', () => { .getAllByDatasetPersistentId(dataset.persistentId, dataset.version) .then((files) => { const expectedTabularData = { - variablesCount: 1, - observationsCount: 0, - unf: 'some' + variablesCount: 7, + observationsCount: 10 } files.forEach((file) => { - expect(file.tabularData).to.deep.equal(expectedTabularData) + expect(file.tabularData?.variablesCount).to.deep.equal( + expectedTabularData.variablesCount + ) + expect(file.tabularData?.observationsCount).to.deep.equal( + expectedTabularData.observationsCount + ) + expect(file.tabularData?.unf).to.not.be.undefined }) }) }) From f9eae82f34d619d0a359af7565ff52e45c3b7063 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 13 Nov 2023 11:28:37 +0100 Subject: [PATCH 14/58] feat(IntegrationFileDeleted): add js-dataverse integration --- .../infrastructure/mappers/JSFileMapper.ts | 6 ++++- .../files/FileJSDataverseRepository.spec.ts | 24 +++++++++++++++++++ .../shared/files/FileHelper.ts | 4 ++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/files/infrastructure/mappers/JSFileMapper.ts b/src/files/infrastructure/mappers/JSFileMapper.ts index 7897ec315..883120aea 100644 --- a/src/files/infrastructure/mappers/JSFileMapper.ts +++ b/src/files/infrastructure/mappers/JSFileMapper.ts @@ -54,7 +54,7 @@ export class JSFileMapper { this.toFileDate(jsFile.creationDate, jsFile.publicationDate, jsFile.embargo), this.toFileDownloads(downloadsCount), this.toFileLabels(jsFile.categories, jsFile.tabularTags), - false, // TODO - Implement this when it is added to js-dataverse + this.toFileIsDeleted(jsFile.deleted), { status: FileIngestStatus.NONE }, // TODO - Implement this when it is added to js-dataverse this.toFileChecksum(jsFile.checksum), this.toFileThumbnail(thumbnail), @@ -245,4 +245,8 @@ export class JSFileMapper { return FileAccessOption.EMBARGOED_RESTRICTED } } + + static toFileIsDeleted(jsFileIsDeleted: boolean | undefined): boolean { + return jsFileIsDeleted ?? false + } } diff --git a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts index 81d6b9540..42683ed1e 100644 --- a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts @@ -100,6 +100,7 @@ describe('File JSDataverse Repository', () => { expect(file.embargo).to.deep.equal(expectedFile.embargo) expect(file.tabularData).to.deep.equal(expectedFile.tabularData) expect(file.description).to.deep.equal(expectedFile.description) + expect(file.isDeleted).to.deep.equal(expectedFile.isDeleted) }) }) }) @@ -440,6 +441,29 @@ describe('File JSDataverse Repository', () => { expect(files.length).to.equal(1) }) }) + + it.skip('gets all the files when they are deleted', async () => { + // This test is failing because js-dataverse deleted property always returns undefined + // TODO: Remove the skip once the issue is fixed + const datasetResponse = await DatasetHelper.createWithFiles(FileHelper.createMany(1)) + + await DatasetHelper.publish(datasetResponse.persistentId) + await TestsUtils.wait(2000) // Wait for the dataset to be published + + const dataset = await datasetRepository.getByPersistentId(datasetResponse.persistentId) + if (!dataset) throw new Error('Dataset not found') + + if (!datasetResponse.files) throw new Error('Files not found') + datasetResponse.files.map((file) => FileHelper.delete(file.id)) + + await fileRepository + .getAllByDatasetPersistentId(dataset.persistentId, dataset.version) + .then((files) => { + files.forEach((file) => { + expect(file.isDeleted).to.equal(true) + }) + }) + }) }) describe('Get file user permissions by id', () => { diff --git a/tests/e2e-integration/shared/files/FileHelper.ts b/tests/e2e-integration/shared/files/FileHelper.ts index 8c29b7fb7..7ae2c1b33 100644 --- a/tests/e2e-integration/shared/files/FileHelper.ts +++ b/tests/e2e-integration/shared/files/FileHelper.ts @@ -152,4 +152,8 @@ export class FileHelper extends DataverseApiHelper { 'multipart/form-data' ) } + + static async delete(id: number) { + return this.request(`/files/${id}`, 'DELETE') + } } From 0b300062638feb0c08d280f6b8f5a1024ee9beb4 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Mon, 13 Nov 2023 15:20:08 -0500 Subject: [PATCH 15/58] feat: add Dataset Dropdown options to AccessDatasetMenu.tsx --- public/locales/en/dataset.json | 5 +- .../access-dataset-menu/AccessDatasetMenu.tsx | 59 ++++++++++++++++++- .../AccessDatasetMenu.stories.tsx | 13 ++++ 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/public/locales/en/dataset.json b/public/locales/en/dataset.json index 916ab30b4..2e847daeb 100644 --- a/public/locales/en/dataset.json +++ b/public/locales/en/dataset.json @@ -53,7 +53,10 @@ } }, "accessDataset": { - "title": "Access Dataset" + "title": "Access Dataset", + "downloadZip": "Download ZIP", + "downloadOriginalZip": "Original Format ZIP", + "downloadArchiveZip": "Archive Format (.tab) ZIP" }, "uploadFiles": "Upload Files" }, diff --git a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx index 542ad49c0..923743376 100644 --- a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx @@ -3,15 +3,23 @@ import { DatasetPublishingStatus, DatasetVersion } from '../../../../dataset/domain/models/Dataset' -import { DropdownButton, DropdownButtonItem } from '@iqss/dataverse-design-system' +import { DropdownButton, DropdownButtonItem, DropdownHeader } from '@iqss/dataverse-design-system' import { useTranslation } from 'react-i18next' +import { Download } from 'react-bootstrap-icons' interface AccessDatasetMenuProps { version: DatasetVersion permissions: DatasetPermissions + datasetContainsTabularFiles?: boolean //TODO: get this from backend + fileSize?: string //TODO: get file size from backend } -export function AccessDatasetMenu({ version, permissions }: AccessDatasetMenuProps) { +export function AccessDatasetMenu({ + version, + permissions, + datasetContainsTabularFiles = true, //TODO: remove default when backend is ready + fileSize = '1.2 GB' //TODO: remove default when backend is ready +}: AccessDatasetMenuProps) { if ( !permissions.canDownloadFiles || (version.publishingStatus === DatasetPublishingStatus.DEACCESSIONED && @@ -19,6 +27,50 @@ export function AccessDatasetMenu({ version, permissions }: AccessDatasetMenuPro ) { return <> } + type DownloadType = 'original' | 'archive' + + const handleDownload = (type: DownloadType) => { + //TODO: implement download feature + console.log(`Downloading ${type} zip file`) + } + const renderDownloadOptions = (datasetContainsTabularFiles: boolean) => { + // Define the options based on whether the dataset contains tabular files + const downloadOptions: { key: string; downloadType: DownloadType; translationKey: string }[] = + datasetContainsTabularFiles + ? [ + { + key: 'original', + downloadType: 'original', + translationKey: 'datasetActionButtons.accessDataset.downloadOriginalZip' + }, + { + key: 'archive', + downloadType: 'archive', + translationKey: 'datasetActionButtons.accessDataset.downloadArchiveZip' + } + ] + : [ + { + key: 'standard', + downloadType: 'original', + translationKey: 'datasetActionButtons.accessDataset.downloadZip' + } + ] + + // Map the options to DropdownButtonItem components + return ( + <> + + Download Options + + {downloadOptions.map((option) => ( + handleDownload(option.downloadType)}> + {t(option.translationKey)} ({fileSize}) + + ))} + + ) + } const { t } = useTranslation('dataset') return ( @@ -27,10 +79,11 @@ export function AccessDatasetMenu({ version, permissions }: AccessDatasetMenuPro title={t('datasetActionButtons.accessDataset.title')} asButtonGroup variant="primary"> - Download + {renderDownloadOptions(datasetContainsTabularFiles)} ) } + // TODO: add download feature https://github.com/IQSS/dataverse-frontend/issues/63 // TODO: add explore feature // TODO: add compute feature diff --git a/src/stories/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.stories.tsx b/src/stories/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.stories.tsx index 8aba8ca0c..15b4d7cfc 100644 --- a/src/stories/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.stories.tsx +++ b/src/stories/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.stories.tsx @@ -23,6 +23,19 @@ type Story = StoryObj export const WithAllPermissions: Story = { render: () => ( + ) +} + +export const WithTabularFiles: Story = { + render: () => ( + From e112c2aac96e0a8a68e2461b7e8963289746d5fb Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Tue, 14 Nov 2023 15:32:31 -0500 Subject: [PATCH 16/58] feat: add FileDownloadSize, update stories and tests --- src/dataset/domain/models/Dataset.ts | 23 +++++- .../infrastructure/mappers/JSDatasetMapper.ts | 4 +- .../DatasetActionButtons.tsx | 8 +- .../access-dataset-menu/AccessDatasetMenu.tsx | 81 ++++++++++++------- .../DatasetActionButtons.stories.tsx | 6 ++ .../AccessDatasetMenu.stories.tsx | 25 ++++-- .../dataset/domain/models/DatasetMother.ts | 31 ++++++- .../mappers/JSDatasetMapper.spec.ts | 6 +- 8 files changed, 141 insertions(+), 43 deletions(-) diff --git a/src/dataset/domain/models/Dataset.ts b/src/dataset/domain/models/Dataset.ts index 409a7ab5e..a0970744c 100644 --- a/src/dataset/domain/models/Dataset.ts +++ b/src/dataset/domain/models/Dataset.ts @@ -280,6 +280,17 @@ export interface PrivateUrl { urlSnippet: string } +export enum FileDownloadSizeMode { + ALL = 'All', + ORIGINAL = 'Original', + ARCHIVAL = 'Archival' +} + +export interface FileDownloadSize { + size: number + fileDownloadSizeMode: FileDownloadSizeMode +} + export class Dataset { constructor( public readonly persistentId: string, @@ -293,10 +304,12 @@ export class Dataset { public readonly permissions: DatasetPermissions, public readonly locks: DatasetLock[], public readonly hasValidTermsOfAccess: boolean, + public readonly hasOneTabularFileAtLeast: boolean, public readonly isValid: boolean, public readonly isReleased: boolean, public readonly thumbnail?: string, - public readonly privateUrl?: PrivateUrl + public readonly privateUrl?: PrivateUrl, + public readonly fileDownloadSizes?: FileDownloadSize[] ) {} public getTitle(): string { @@ -339,10 +352,12 @@ export class Dataset { public readonly permissions: DatasetPermissions, public readonly locks: DatasetLock[], public readonly hasValidTermsOfAccess: boolean, + public readonly hasOneTabularFileAtLeast: boolean, public readonly isValid: boolean, public readonly isReleased: boolean, public readonly thumbnail?: string, - public readonly privateUrl?: PrivateUrl + public readonly privateUrl?: PrivateUrl, + public readonly fileDownloadSizes?: FileDownloadSize[] ) { this.withLabels() this.withAlerts() @@ -455,10 +470,12 @@ export class Dataset { this.permissions, this.locks, this.hasValidTermsOfAccess, + this.hasOneTabularFileAtLeast, this.isValid, this.isReleased, this.thumbnail, - this.privateUrl + this.privateUrl, + this.fileDownloadSizes ) } } diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index 77a8c3031..f8ebf5f97 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -47,11 +47,13 @@ export class JSDatasetMapper { }, // TODO Connect with dataset permissions [], // TODO Connect with dataset locks true, // TODO Connect with dataset hasValidTermsOfAccess + true, // TODO Connect with dataset hasOneTabularFileAtLeast true, // TODO Connect with dataset isValid jsDataset.versionInfo.releaseTime !== undefined && !isNaN(jsDataset.versionInfo.releaseTime.getTime()), // TODO Connect with dataset isReleased, undefined, // TODO: get dataset thumbnail from Dataverse https://github.com/IQSS/dataverse-frontend/issues/203 - privateUrl + privateUrl, + [] // TODO: Connect with file download use case ).build() } diff --git a/src/sections/dataset/dataset-action-buttons/DatasetActionButtons.tsx b/src/sections/dataset/dataset-action-buttons/DatasetActionButtons.tsx index ce57675e0..c95f2c008 100644 --- a/src/sections/dataset/dataset-action-buttons/DatasetActionButtons.tsx +++ b/src/sections/dataset/dataset-action-buttons/DatasetActionButtons.tsx @@ -14,9 +14,15 @@ interface DatasetActionButtonsProps { export function DatasetActionButtons({ dataset }: DatasetActionButtonsProps) { const { t } = useTranslation('dataset') + console.log(JSON.stringify(dataset.fileDownloadSizes)) return ( - + diff --git a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx index 923743376..002dbd8c5 100644 --- a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx @@ -1,7 +1,9 @@ import { DatasetPermissions, DatasetPublishingStatus, - DatasetVersion + DatasetVersion, + FileDownloadSize, + FileDownloadSizeMode } from '../../../../dataset/domain/models/Dataset' import { DropdownButton, DropdownButtonItem, DropdownHeader } from '@iqss/dataverse-design-system' import { useTranslation } from 'react-i18next' @@ -10,15 +12,15 @@ import { Download } from 'react-bootstrap-icons' interface AccessDatasetMenuProps { version: DatasetVersion permissions: DatasetPermissions - datasetContainsTabularFiles?: boolean //TODO: get this from backend - fileSize?: string //TODO: get file size from backend + hasOneTabularFileAtLeast: boolean + fileDownloadSizes?: FileDownloadSize[] } export function AccessDatasetMenu({ version, permissions, - datasetContainsTabularFiles = true, //TODO: remove default when backend is ready - fileSize = '1.2 GB' //TODO: remove default when backend is ready + hasOneTabularFileAtLeast, + fileDownloadSizes }: AccessDatasetMenuProps) { if ( !permissions.canDownloadFiles || @@ -27,35 +29,49 @@ export function AccessDatasetMenu({ ) { return <> } - type DownloadType = 'original' | 'archive' - const handleDownload = (type: DownloadType) => { + function formatFileSize(bytes: number): string { + if (bytes < 1024) { + return `${bytes} B` + } else if (bytes < 1024 * 1024) { + return `${(bytes / 1024).toFixed(2)} KB` + } else if (bytes < 1024 * 1024 * 1024) { + return `${(bytes / 1024 / 1024).toFixed(2)} MB` + } else { + return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB` + } + } + + const handleDownload = (type: FileDownloadSizeMode) => { //TODO: implement download feature console.log(`Downloading ${type} zip file`) } const renderDownloadOptions = (datasetContainsTabularFiles: boolean) => { // Define the options based on whether the dataset contains tabular files - const downloadOptions: { key: string; downloadType: DownloadType; translationKey: string }[] = - datasetContainsTabularFiles - ? [ - { - key: 'original', - downloadType: 'original', - translationKey: 'datasetActionButtons.accessDataset.downloadOriginalZip' - }, - { - key: 'archive', - downloadType: 'archive', - translationKey: 'datasetActionButtons.accessDataset.downloadArchiveZip' - } - ] - : [ - { - key: 'standard', - downloadType: 'original', - translationKey: 'datasetActionButtons.accessDataset.downloadZip' - } - ] + const downloadOptions: { + key: string + downloadType: FileDownloadSizeMode + translationKey: string + }[] = datasetContainsTabularFiles + ? [ + { + key: 'original', + downloadType: FileDownloadSizeMode.ORIGINAL, + translationKey: 'datasetActionButtons.accessDataset.downloadOriginalZip' + }, + { + key: 'archive', + downloadType: FileDownloadSizeMode.ARCHIVAL, + translationKey: 'datasetActionButtons.accessDataset.downloadArchiveZip' + } + ] + : [ + { + key: 'standard', + downloadType: FileDownloadSizeMode.ORIGINAL, + translationKey: 'datasetActionButtons.accessDataset.downloadZip' + } + ] // Map the options to DropdownButtonItem components return ( @@ -65,7 +81,12 @@ export function AccessDatasetMenu({ {downloadOptions.map((option) => ( handleDownload(option.downloadType)}> - {t(option.translationKey)} ({fileSize}) + {t(option.translationKey)} ( + {formatFileSize( + fileDownloadSizes?.find((size) => size.fileDownloadSizeMode === option.downloadType) + ?.size || 0 + )} + ) ))} @@ -79,7 +100,7 @@ export function AccessDatasetMenu({ title={t('datasetActionButtons.accessDataset.title')} asButtonGroup variant="primary"> - {renderDownloadOptions(datasetContainsTabularFiles)} + {renderDownloadOptions(hasOneTabularFileAtLeast)} ) } diff --git a/src/stories/dataset/dataset-action-buttons/DatasetActionButtons.stories.tsx b/src/stories/dataset/dataset-action-buttons/DatasetActionButtons.stories.tsx index 173f83561..9226ec599 100644 --- a/src/stories/dataset/dataset-action-buttons/DatasetActionButtons.stories.tsx +++ b/src/stories/dataset/dataset-action-buttons/DatasetActionButtons.stories.tsx @@ -3,6 +3,7 @@ import { WithI18next } from '../../WithI18next' import { WithSettings } from '../../WithSettings' import { DatasetActionButtons } from '../../../sections/dataset/dataset-action-buttons/DatasetActionButtons' import { + DatasetFileDownloadSizeMother, DatasetMother, DatasetPermissionsMother, DatasetVersionMother @@ -28,6 +29,11 @@ export const WithPublishPermissions: Story = { permissions: DatasetPermissionsMother.createWithAllAllowed(), version: DatasetVersionMother.createDraftAsLatestVersion(), hasValidTermsOfAccess: true, + hasOneTabularFileAtLeast: true, + fileDownloadSizes: [ + DatasetFileDownloadSizeMother.createOriginal(), + DatasetFileDownloadSizeMother.createArchival() + ], isValid: true, isReleased: true })} diff --git a/src/stories/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.stories.tsx b/src/stories/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.stories.tsx index 15b4d7cfc..402e9753e 100644 --- a/src/stories/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.stories.tsx +++ b/src/stories/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.stories.tsx @@ -2,6 +2,7 @@ import { Meta, StoryObj } from '@storybook/react' import { WithI18next } from '../../../WithI18next' import { WithSettings } from '../../../WithSettings' import { + DatasetFileDownloadSizeMother, DatasetPermissionsMother, DatasetVersionMother } from '../../../../../tests/component/dataset/domain/models/DatasetMother' @@ -20,24 +21,36 @@ const meta: Meta = { export default meta type Story = StoryObj -export const WithAllPermissions: Story = { +export const WithDownloadNotAllowed: Story = { render: () => ( + ) +} +export const WithoutTabularFiles: Story = { + render: () => ( + ) } - export const WithTabularFiles: Story = { render: () => ( ) } diff --git a/tests/component/dataset/domain/models/DatasetMother.ts b/tests/component/dataset/domain/models/DatasetMother.ts index bca8773c5..30d6adadb 100644 --- a/tests/component/dataset/domain/models/DatasetMother.ts +++ b/tests/component/dataset/domain/models/DatasetMother.ts @@ -10,8 +10,10 @@ import { DatasetPermissions, DatasetPublishingStatus, DatasetVersion, + FileDownloadSize, MetadataBlockName } from '../../../../../src/dataset/domain/models/Dataset' +import { FileDownloadSizeMode } from '@iqss/dataverse-client-javascript' export class DatasetVersionMother { static create(props?: Partial): DatasetVersion { @@ -185,6 +187,24 @@ export class DatasetLockMother { } } +export class DatasetFileDownloadSizeMother { + static create(props?: Partial): FileDownloadSize { + return { + size: faker.datatype.number(), + fileDownloadSizeMode: faker.helpers.arrayElement(Object.values(FileDownloadSizeMode)), + ...props + } + } + + static createArchival(): FileDownloadSize { + return this.create({ fileDownloadSizeMode: FileDownloadSizeMode.ARCHIVAL }) + } + + static createOriginal(): FileDownloadSize { + return this.create({ fileDownloadSizeMode: FileDownloadSizeMode.ORIGINAL }) + } +} + export class DatasetMother { static createEmpty(): undefined { return undefined @@ -285,10 +305,12 @@ export class DatasetMother { permissions: DatasetPermissionsMother.create(), locks: [], hasValidTermsOfAccess: faker.datatype.boolean(), + hasOneTabularFileAtLeast: faker.datatype.boolean(), isValid: faker.datatype.boolean(), isReleased: faker.datatype.boolean(), thumbnail: undefined, privateUrl: undefined, + fileDownloadSizes: undefined, ...props } @@ -302,10 +324,12 @@ export class DatasetMother { dataset.permissions, dataset.locks, dataset.hasValidTermsOfAccess, + dataset.hasOneTabularFileAtLeast, dataset.isValid, dataset.isReleased, dataset.thumbnail, - dataset.privateUrl + dataset.privateUrl, + dataset.fileDownloadSizes ).build() } @@ -437,6 +461,11 @@ export class DatasetMother { locks: [], isReleased: true, hasValidTermsOfAccess: true, + hasOneTabularFileAtLeast: true, + fileDownloadSizes: [ + { size: 219829, fileDownloadSizeMode: FileDownloadSizeMode.ORIGINAL }, + { size: 253629, fileDownloadSizeMode: FileDownloadSizeMode.ARCHIVAL } + ], isValid: true, ...props }) diff --git a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts index 699c3d8a7..37b813b75 100644 --- a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts +++ b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts @@ -118,10 +118,12 @@ const expectedDataset = { }, locks: [], hasValidTermsOfAccess: true, + hasOneTabularFileAtLeast: true, isValid: true, isReleased: false, thumbnail: undefined, - privateUrl: undefined + privateUrl: undefined, + fileDownloadSizes: [] } const expectedDatasetAlternateVersion = { persistentId: 'doi:10.5072/FK2/B4B2MJ', @@ -138,9 +140,11 @@ const expectedDatasetAlternateVersion = { citation: 'Finch, Fiona, 2023, "Darwin\'s Finches", https://doi.org/10.5072/FK2/B4B2MJ, Root, DRAFT VERSION', hasValidTermsOfAccess: true, + hasOneTabularFileAtLeast: true, isReleased: false, isValid: true, privateUrl: undefined, + fileDownloadSizes: [], labels: [ { semanticMeaning: 'dataset', value: 'Draft' }, { semanticMeaning: 'warning', value: 'Unpublished' } From 36baaf64a62e0d44079d9cb18fd5f6829700b5c3 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Wed, 15 Nov 2023 12:36:31 -0500 Subject: [PATCH 17/58] feat: update unit test --- .../AccessDatasetMenu.spec.tsx | 97 ++++++++++++++++--- 1 file changed, 86 insertions(+), 11 deletions(-) diff --git a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx index c30dac356..7132badfd 100644 --- a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx +++ b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx @@ -1,5 +1,6 @@ import { AccessDatasetMenu } from '../../../../../../src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu' import { + DatasetFileDownloadSizeMother, DatasetPermissionsMother, DatasetVersionMother } from '../../../../dataset/domain/models/DatasetMother' @@ -8,8 +9,18 @@ describe('AccessDatasetMenu', () => { it('renders the AccessDatasetMenu if the user has download files permissions and the dataset is not deaccessioned', () => { const version = DatasetVersionMother.createReleased() const permissions = DatasetPermissionsMother.createWithFilesDownloadAllowed() - - cy.customMount() + const fileDownloadSizes = [ + DatasetFileDownloadSizeMother.createOriginal(), + DatasetFileDownloadSizeMother.createArchival() + ] + cy.customMount( + + ) cy.findByRole('button', { name: 'Access Dataset' }).should('exist') }) @@ -20,27 +31,91 @@ describe('AccessDatasetMenu', () => { canUpdateDataset: true, canDownloadFiles: true }) - - cy.customMount() - + const fileDownloadSizes = [ + DatasetFileDownloadSizeMother.createOriginal(), + DatasetFileDownloadSizeMother.createArchival() + ] + cy.customMount( + + ) cy.findByRole('button', { name: 'Access Dataset' }).should('exist') }) it('does not render the AccessDatasetMenu if the user do not have download files permissions', () => { const version = DatasetVersionMother.create() const permissions = DatasetPermissionsMother.createWithFilesDownloadNotAllowed() - - cy.customMount() - + const fileDownloadSizes = [ + DatasetFileDownloadSizeMother.createOriginal(), + DatasetFileDownloadSizeMother.createArchival() + ] + cy.customMount( + + ) cy.findByRole('button', { name: 'Access Dataset' }).should('not.exist') }) it('does not render the AccessDatasetMenu if the dataset is deaccessioned and the user does not have update dataset permissions', () => { const version = DatasetVersionMother.createDeaccessioned() const permissions = DatasetPermissionsMother.createWithUpdateDatasetNotAllowed() - - cy.customMount() - + const fileDownloadSizes = [ + DatasetFileDownloadSizeMother.createOriginal(), + DatasetFileDownloadSizeMother.createArchival() + ] + cy.customMount( + + ) cy.findByRole('button', { name: 'Access Dataset' }).should('not.exist') }) + it('displays one dropdown option if there are no tabular files', () => { + const version = DatasetVersionMother.createReleased() + const permissions = DatasetPermissionsMother.createWithFilesDownloadAllowed() + const fileDownloadSizes = [DatasetFileDownloadSizeMother.createOriginal()] + // const menuItemName = 'Download ZIP' + ' (' + formatFileSize(fileDownloadSizes[0].size) + ')' + cy.customMount( + + ) + cy.findByRole('button', { name: 'Access Dataset' }).should('exist') + cy.findByRole('button', { name: 'Access Dataset' }).click() + cy.findByText(/Download ZIP \(\d+(\.\d+)? KB\)/).should('exist') + }) + it('displays two dropdown options if there is at least one', () => { + const version = DatasetVersionMother.createReleased() + const permissions = DatasetPermissionsMother.createWithFilesDownloadAllowed() + const fileDownloadSizes = [ + DatasetFileDownloadSizeMother.createOriginal(), + DatasetFileDownloadSizeMother.createArchival() + ] + cy.customMount( + + ) + cy.findByRole('button', { name: 'Access Dataset' }).should('exist') + cy.findByRole('button', { name: 'Access Dataset' }).click() + cy.findByText(/Original Format ZIP \(\d+(\.\d+)? KB\)/).should('exist') + cy.findByText(/Archive Format \(\.tab\) ZIP \(\d+(\.\d+)? KB\)/).should('exist') + }) }) From 775826da8adfbf2df6fe199662b0b00eaa5b7df8 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Thu, 16 Nov 2023 15:01:51 -0500 Subject: [PATCH 18/58] feat: add NotImplementedModal and story --- .../not-implemented/NotImplementedModal.tsx | 24 ++++++++++++++ .../NotImplementedModal.module.scss | 5 +++ .../NotImplementedModal.stories.tsx | 32 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 src/sections/not-implemented/NotImplementedModal.tsx create mode 100644 src/stories/not-implemented/NotImplementedModal.module.scss create mode 100644 src/stories/not-implemented/NotImplementedModal.stories.tsx diff --git a/src/sections/not-implemented/NotImplementedModal.tsx b/src/sections/not-implemented/NotImplementedModal.tsx new file mode 100644 index 000000000..3be551cec --- /dev/null +++ b/src/sections/not-implemented/NotImplementedModal.tsx @@ -0,0 +1,24 @@ +import { Button, Modal } from '@iqss/dataverse-design-system' + +interface NotImplementedModalProps { + show: boolean + handleClose: () => void +} + +export function NotImplementedModal({ show, handleClose }: NotImplementedModalProps) { + return ( + + + Not Implemented + + +

This feature is not implemented yet.

+
+ + + +
+ ) +} diff --git a/src/stories/not-implemented/NotImplementedModal.module.scss b/src/stories/not-implemented/NotImplementedModal.module.scss new file mode 100644 index 000000000..97f799fbe --- /dev/null +++ b/src/stories/not-implemented/NotImplementedModal.module.scss @@ -0,0 +1,5 @@ +@import "node_modules/@iqss/dataverse-design-system/src/lib/assets/styles/design-tokens/colors.module"; + +.paragraph { + color: $dv-danger-color; +} \ No newline at end of file diff --git a/src/stories/not-implemented/NotImplementedModal.stories.tsx b/src/stories/not-implemented/NotImplementedModal.stories.tsx new file mode 100644 index 000000000..559d300c0 --- /dev/null +++ b/src/stories/not-implemented/NotImplementedModal.stories.tsx @@ -0,0 +1,32 @@ +import { Meta, StoryObj } from '@storybook/react' +import { NotImplementedModal } from '../../sections/not-implemented/NotImplementedModal' + +import { useState } from 'react' +import { Button } from '@iqss/dataverse-design-system' + +const meta: Meta = { + title: 'Sections/NotImplementedModal', + component: NotImplementedModal +} + +export default meta + +type Story = StoryObj + +function DefaultExample() { + const [show, setShow] = useState(false) + + const handleClose = () => setShow(false) + const handleShow = () => setShow(true) + return ( + <> + + + + + ) +} + +export const Default: Story = { + render: () => DefaultExample() +} From 1d5e3986b9373cd56269fe5fdb422568b9fe379b Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Thu, 16 Nov 2023 16:25:09 -0500 Subject: [PATCH 19/58] fix: remove unused file --- src/stories/not-implemented/NotImplementedModal.module.scss | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 src/stories/not-implemented/NotImplementedModal.module.scss diff --git a/src/stories/not-implemented/NotImplementedModal.module.scss b/src/stories/not-implemented/NotImplementedModal.module.scss deleted file mode 100644 index 97f799fbe..000000000 --- a/src/stories/not-implemented/NotImplementedModal.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -@import "node_modules/@iqss/dataverse-design-system/src/lib/assets/styles/design-tokens/colors.module"; - -.paragraph { - color: $dv-danger-color; -} \ No newline at end of file From 028cadad12e33550818351eb1cbf28ebc652cfd6 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Thu, 16 Nov 2023 16:26:22 -0500 Subject: [PATCH 20/58] feat: add NotImplementedModalContext --- .../edit-dataset-menu/EditDatasetMenu.tsx | 4 +++- .../NotImplementedModalContext.ts | 15 +++++++++++++++ .../NotImplementedModalProvider.tsx | 14 ++++++++++++++ src/stories/WIthNotImplementedModal.tsx | 10 ++++++++++ .../NotImplementedModal.stories.tsx | 16 +++++++--------- 5 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 src/sections/not-implemented/NotImplementedModalContext.ts create mode 100644 src/sections/not-implemented/NotImplementedModalProvider.tsx create mode 100644 src/stories/WIthNotImplementedModal.tsx diff --git a/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx index 326518a5e..58b7f684d 100644 --- a/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx @@ -4,6 +4,7 @@ import { EditDatasetPermissionsMenu } from './EditDatasetPermissionsMenu' import { DeleteDatasetButton } from './DeleteDatasetButton' import { DeaccessionDatasetButton } from './DeaccessionDatasetButton' import { useTranslation } from 'react-i18next' +import { useNotImplementedModal } from '../../../not-implemented/NotImplementedModalContext' interface EditDatasetMenuProps { dataset: Dataset @@ -13,10 +14,11 @@ export function EditDatasetMenu({ dataset }: EditDatasetMenuProps) { if (!dataset.permissions.canUpdateDataset) { return <> } - + const { showModal } = useNotImplementedModal() const { t } = useTranslation('dataset') return ( void + hideModal: () => void + isModalOpen: boolean +} + +export const NotImplementedModal = createContext({ + isModalOpen: false, + showModal: /* istanbul ignore next */ () => {}, + hideModal: /* istanbul ignore next */ () => {} +}) + +export const useNotImplementedModal = () => useContext(NotImplementedModal) diff --git a/src/sections/not-implemented/NotImplementedModalProvider.tsx b/src/sections/not-implemented/NotImplementedModalProvider.tsx new file mode 100644 index 000000000..3a18ce32e --- /dev/null +++ b/src/sections/not-implemented/NotImplementedModalProvider.tsx @@ -0,0 +1,14 @@ +import { useState, PropsWithChildren } from 'react' +import { NotImplementedModal } from './NotImplementedModalContext' + +export function NotImplementedModalProvider({ children }: PropsWithChildren) { + const [isModalOpen, setIsModalOpen] = useState(false) + const showModal = () => setIsModalOpen(true) + const hideModal = () => setIsModalOpen(false) + + return ( + + {children} + + ) +} diff --git a/src/stories/WIthNotImplementedModal.tsx b/src/stories/WIthNotImplementedModal.tsx new file mode 100644 index 000000000..701fa83c7 --- /dev/null +++ b/src/stories/WIthNotImplementedModal.tsx @@ -0,0 +1,10 @@ +import { StoryFn } from '@storybook/react' +import { NotImplementedModalProvider } from '../sections/not-implemented/NotImplementedModalProvider' + +export const WIthNotImplementedModal = (Story: StoryFn) => { + return ( + + + + ) +} diff --git a/src/stories/not-implemented/NotImplementedModal.stories.tsx b/src/stories/not-implemented/NotImplementedModal.stories.tsx index 559d300c0..952c1a2ed 100644 --- a/src/stories/not-implemented/NotImplementedModal.stories.tsx +++ b/src/stories/not-implemented/NotImplementedModal.stories.tsx @@ -1,12 +1,13 @@ import { Meta, StoryObj } from '@storybook/react' import { NotImplementedModal } from '../../sections/not-implemented/NotImplementedModal' - -import { useState } from 'react' import { Button } from '@iqss/dataverse-design-system' +import { useNotImplementedModal } from '../../sections/not-implemented/NotImplementedModalContext' +import { WIthNotImplementedModal } from '../WIthNotImplementedModal' const meta: Meta = { title: 'Sections/NotImplementedModal', - component: NotImplementedModal + component: NotImplementedModal, + decorators: [WIthNotImplementedModal] } export default meta @@ -14,15 +15,12 @@ export default meta type Story = StoryObj function DefaultExample() { - const [show, setShow] = useState(false) - - const handleClose = () => setShow(false) - const handleShow = () => setShow(true) + const { showModal, hideModal, isModalOpen } = useNotImplementedModal() return ( <> - + - + ) } From 6f0e5b47901a86c1114246e7b1e8c103243da172 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Thu, 16 Nov 2023 16:33:09 -0500 Subject: [PATCH 21/58] feat: add NotImplementedModal to Dataset.tsx --- src/sections/dataset/Dataset.tsx | 4 ++++ src/stories/dataset/Dataset.stories.tsx | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/sections/dataset/Dataset.tsx b/src/sections/dataset/Dataset.tsx index c5444efb3..54e2d76c7 100644 --- a/src/sections/dataset/Dataset.tsx +++ b/src/sections/dataset/Dataset.tsx @@ -14,6 +14,8 @@ import { DatasetActionButtons } from './dataset-action-buttons/DatasetActionButt import { useDataset } from './DatasetContext' import { useEffect } from 'react' import { DatasetAlerts } from './dataset-alerts/DatasetAlerts' +import { useNotImplementedModal } from '../not-implemented/NotImplementedModalContext' +import { NotImplementedModal } from '../not-implemented/NotImplementedModal' interface DatasetProps { fileRepository: FileRepository @@ -23,6 +25,7 @@ export function Dataset({ fileRepository }: DatasetProps) { const { setIsLoading } = useLoading() const { dataset, isLoading } = useDataset() const { t } = useTranslation('dataset') + const { showModal, hideModal, isModalOpen } = useNotImplementedModal() useEffect(() => { setIsLoading(isLoading) @@ -34,6 +37,7 @@ export function Dataset({ fileRepository }: DatasetProps) { return ( <> + {!dataset ? ( ) : ( diff --git a/src/stories/dataset/Dataset.stories.tsx b/src/stories/dataset/Dataset.stories.tsx index ab3c7e26e..935433515 100644 --- a/src/stories/dataset/Dataset.stories.tsx +++ b/src/stories/dataset/Dataset.stories.tsx @@ -15,6 +15,7 @@ import { WithDatasetDraftAsOwner } from './WithDatasetDraftAsOwner' import { WithDatasetNotFound } from './WithDatasetNotFound' import { WithDatasetLoading } from './WithDatasetLoading' import { WithLoggedInUser } from '../WithLoggedInUser' +import { WIthNotImplementedModal } from '../WIthNotImplementedModal' const meta: Meta = { title: 'Pages/Dataset', @@ -35,11 +36,23 @@ export const Default: Story = { } export const DraftWithAllDatasetPermissions: Story = { - decorators: [WithLayout, WithDatasetDraftAsOwner, WithLoggedInUser, WithFilePermissionsGranted], + decorators: [ + WithLayout, + WithDatasetDraftAsOwner, + WithLoggedInUser, + WithFilePermissionsGranted, + WIthNotImplementedModal + ], render: () => } export const LoggedInAsOwner: Story = { - decorators: [WithDataset, WithLayout, WithLoggedInUser, WithFilePermissionsGranted], + decorators: [ + WithDataset, + WithLayout, + WithLoggedInUser, + WithFilePermissionsGranted, + WIthNotImplementedModal + ], render: () => } From 8020ce5293cfc09ac1dba17d305b57e95c77e6a2 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Thu, 16 Nov 2023 16:34:04 -0500 Subject: [PATCH 22/58] fix: remove unused constant --- src/sections/dataset/Dataset.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sections/dataset/Dataset.tsx b/src/sections/dataset/Dataset.tsx index 54e2d76c7..8095e0ae8 100644 --- a/src/sections/dataset/Dataset.tsx +++ b/src/sections/dataset/Dataset.tsx @@ -25,7 +25,7 @@ export function Dataset({ fileRepository }: DatasetProps) { const { setIsLoading } = useLoading() const { dataset, isLoading } = useDataset() const { t } = useTranslation('dataset') - const { showModal, hideModal, isModalOpen } = useNotImplementedModal() + const { hideModal, isModalOpen } = useNotImplementedModal() useEffect(() => { setIsLoading(isLoading) From 7ea59f1249f5c0655ec9d8107e89cb7a9de3c749 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 17 Nov 2023 09:40:13 -0500 Subject: [PATCH 23/58] feat: add modal to EditFilesOptions.tsx --- .../file-actions/edit-files-menu/EditFilesOptions.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesOptions.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesOptions.tsx index 1377303f5..4f21ec3a2 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesOptions.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesOptions.tsx @@ -4,21 +4,27 @@ import { useTranslation } from 'react-i18next' import { useState } from 'react' import { FileSelection } from '../../row-selection/useFileSelection' import { NoSelectedFilesModal } from '../no-selected-files-modal/NoSelectedFilesModal' +import { useNotImplementedModal } from '../../../../../not-implemented/NotImplementedModalContext' interface EditFileOptionsProps { files: File[] fileSelection: FileSelection } + 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 { showModal } = useNotImplementedModal() const onClick = () => { if (Object.keys(fileSelection).length === SELECTED_FILES_EMPTY) { setShowNoFilesSelectedModal(true) + } else { + // TODO - Implement edit files + showModal() } } From f6f3ab4ecc51cc1f470f7d253f7f5fce10689e07 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 17 Nov 2023 17:41:28 -0500 Subject: [PATCH 24/58] feat: add Modal to DatasetFactory.tsx, update modal text --- src/sections/dataset/DatasetFactory.tsx | 13 ++++++++----- .../DatasetUploadFilesButton.tsx | 4 +++- .../not-implemented/NotImplementedModal.tsx | 12 ++++++++++-- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/sections/dataset/DatasetFactory.tsx b/src/sections/dataset/DatasetFactory.tsx index 1ac3e1e15..4a071df25 100644 --- a/src/sections/dataset/DatasetFactory.tsx +++ b/src/sections/dataset/DatasetFactory.tsx @@ -11,6 +11,7 @@ import { SettingJSDataverseRepository } from '../../settings/infrastructure/Sett import { FilePermissionsProvider } from '../file/file-permissions/FilePermissionsProvider' import { SettingsProvider } from '../settings/SettingsProvider' import { DatasetProvider } from './DatasetProvider' +import { NotImplementedModalProvider } from '../not-implemented/NotImplementedModalProvider' const datasetRepository = new DatasetJSDataverseRepository() const fileRepository = new FileJSDataverseRepository() @@ -22,11 +23,13 @@ export class DatasetFactory { return ( - - - - - + + + + + + + ) diff --git a/src/sections/dataset/dataset-files/dataset-upload-files-button/DatasetUploadFilesButton.tsx b/src/sections/dataset/dataset-files/dataset-upload-files-button/DatasetUploadFilesButton.tsx index 3f33cc8fb..e32d08fe6 100644 --- a/src/sections/dataset/dataset-files/dataset-upload-files-button/DatasetUploadFilesButton.tsx +++ b/src/sections/dataset/dataset-files/dataset-upload-files-button/DatasetUploadFilesButton.tsx @@ -4,6 +4,7 @@ import { useSession } from '../../../session/SessionContext' import styles from './DatasetUploadFilesButton.module.scss' import { useTranslation } from 'react-i18next' import { useDataset } from '../../DatasetContext' +import { useNotImplementedModal } from '../../../not-implemented/NotImplementedModalContext' export function DatasetUploadFilesButton() { const { t } = useTranslation('dataset') @@ -11,8 +12,9 @@ export function DatasetUploadFilesButton() { const { dataset } = useDataset() const handleClick = () => { // TODO - Implement upload files + showModal() } - + const { showModal } = useNotImplementedModal() if (!user || !dataset?.permissions.canUpdateDataset) { return <> } diff --git a/src/sections/not-implemented/NotImplementedModal.tsx b/src/sections/not-implemented/NotImplementedModal.tsx index 3be551cec..6b691e611 100644 --- a/src/sections/not-implemented/NotImplementedModal.tsx +++ b/src/sections/not-implemented/NotImplementedModal.tsx @@ -6,13 +6,21 @@ interface NotImplementedModalProps { } export function NotImplementedModal({ show, handleClose }: NotImplementedModalProps) { + const protocol = window.location.protocol + const host = window.location.host + const baseUrl = `${protocol}//${host}` + return ( - + Not Implemented -

This feature is not implemented yet.

+

This feature is not implemented yet in SPA.

+

+ If you want to use this feature you can go to the original{' '} + Dataverse page. +

+
) } diff --git a/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx index 58ec23cd8..c89a54210 100644 --- a/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx @@ -2,6 +2,7 @@ import { Dataset, DatasetPublishingStatus } from '../../../../dataset/domain/mod import { DropdownButton, DropdownButtonItem } from '@iqss/dataverse-design-system' import { ChangeCurationStatusMenu } from './ChangeCurationStatusMenu' import { useTranslation } from 'react-i18next' +import { useNotImplementedModal } from '../../../not-implemented/NotImplementedModalContext' interface PublishDatasetMenuProps { dataset: Dataset @@ -17,10 +18,17 @@ export function PublishDatasetMenu({ dataset }: PublishDatasetMenuProps) { } const { t } = useTranslation('dataset') + const handleSelect = () => { + // TODO - Implement upload files + showModal() + } + const { showModal } = useNotImplementedModal() + return ( { + // TODO - Implement upload files + showModal() + } + const { showModal } = useNotImplementedModal() if ( files.length < MINIMUM_FILES_COUNT_TO_SHOW_DOWNLOAD_FILES_BUTTON || !dataset?.permissions.canDownloadFiles @@ -30,6 +36,8 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto const onClick = () => { if (Object.keys(fileSelection).length === SELECTED_FILES_EMPTY) { setShowNoFilesSelectedModal(true) + } else { + handleClick() } } From 9146a2cbaa51a19b850e2d3c7246967ffe7a8c3b Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Mon, 27 Nov 2023 19:40:27 -0500 Subject: [PATCH 33/58] fix: add unit test --- .../not-implemented/NotImplemented.spec.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/component/sections/not-implemented/NotImplemented.spec.tsx diff --git a/tests/component/sections/not-implemented/NotImplemented.spec.tsx b/tests/component/sections/not-implemented/NotImplemented.spec.tsx new file mode 100644 index 000000000..d7bc17c8c --- /dev/null +++ b/tests/component/sections/not-implemented/NotImplemented.spec.tsx @@ -0,0 +1,29 @@ +import { NotImplementedModal } from '../../../../src/sections/not-implemented/NotImplementedModal' +import { mount } from 'cypress/react18' + +describe('NotImplementedModal Component', () => { + it('renders the modal when show is true', () => { + // Mount the component with show set to true + mount( {}} />) + + // Check if the modal title is present + cy.findByText('Not Implemented').should('exist') + + // Check if the modal body has specific content + cy.findByText('This feature is not implemented yet in SPA.').should('exist') + }) + + it('closes the modal when the Close button is clicked', () => { + // A spy to check if handleClose is called + const handleClose = cy.spy().as('handleClose') + + // Mount the component with the spy as the handleClose prop + mount() + + // Click the Close button + cy.findByText('Close').click() + + // Check if handleClose was called + cy.get('@handleClose').should('have.been.calledOnce') + }) +}) From 178bd71362acfbd0b8791e5513b3e0af8cdcfdd7 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 28 Nov 2023 13:44:09 +0100 Subject: [PATCH 34/58] feat(IntegrationFileDownload): connect use case with dataverse API --- src/files/domain/models/File.ts | 5 +- .../domain/repositories/FileRepository.ts | 1 - src/files/domain/useCases/getFile.ts | 10 -- .../FileJSDataverseRepository.ts | 16 --- .../infrastructure/mappers/JSFileMapper.ts | 13 ++- src/sections/dataset/DatasetFactory.tsx | 21 ++-- .../FileNonTabularDownloadOptions.tsx | 4 +- .../FileDownloadHelperContext.ts | 11 -- .../FileDownloadHelperProvider.tsx | 23 ---- .../file-download-helper/useFileDownload.tsx | 19 ---- .../DatasetCitation.stories.tsx | 1 - .../files/FileMockLoadingRepository.ts | 9 -- src/stories/files/FileMockNoDataRepository.ts | 9 -- .../files/FileMockNoFiltersRepository.ts | 10 -- src/stories/files/FileMockRepository.ts | 10 -- .../files/domain/models/FileMother.ts | 8 +- .../FileDownloadOptions.spec.tsx | 2 +- .../FileNonTabularDownloadOptions.spec.tsx | 30 ++---- .../files/FileJSDataverseRepository.spec.ts | 101 +++++++----------- 19 files changed, 74 insertions(+), 229 deletions(-) delete mode 100644 src/files/domain/useCases/getFile.ts delete mode 100644 src/sections/file/file-download-helper/FileDownloadHelperContext.ts delete mode 100644 src/sections/file/file-download-helper/FileDownloadHelperProvider.tsx delete mode 100644 src/sections/file/file-download-helper/useFileDownload.tsx diff --git a/src/files/domain/models/File.ts b/src/files/domain/models/File.ts index b6f685c75..c83274844 100644 --- a/src/files/domain/models/File.ts +++ b/src/files/domain/models/File.ts @@ -158,12 +158,13 @@ export class File { readonly labels: FileLabel[], public readonly isDeleted: boolean, public readonly ingest: FileIngest, - readonly checksum?: FileChecksum, + public readonly originalFileDownloadUrl: string, public thumbnail?: string, readonly directory?: string, readonly embargo?: FileEmbargo, readonly tabularData?: FileTabularData, - readonly description?: string + readonly description?: string, + readonly checksum?: FileChecksum ) {} getLink(): string { diff --git a/src/files/domain/repositories/FileRepository.ts b/src/files/domain/repositories/FileRepository.ts index b913a5bb7..976fd5a66 100644 --- a/src/files/domain/repositories/FileRepository.ts +++ b/src/files/domain/repositories/FileRepository.ts @@ -23,5 +23,4 @@ export interface FileRepository { criteria?: FileCriteria ) => Promise getUserPermissionsById: (id: number) => Promise - getOriginalFileById: (id: number) => Promise } diff --git a/src/files/domain/useCases/getFile.ts b/src/files/domain/useCases/getFile.ts deleted file mode 100644 index 96f5f01c7..000000000 --- a/src/files/domain/useCases/getFile.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { FileRepository } from '../repositories/FileRepository' - -export async function getFile( - fileRepository: FileRepository, - id: number -): Promise { - return fileRepository.getOriginalFileById(id).catch((error: Error) => { - throw new Error(error.message) - }) -} diff --git a/src/files/infrastructure/FileJSDataverseRepository.ts b/src/files/infrastructure/FileJSDataverseRepository.ts index fe1328b1d..e16c3c004 100644 --- a/src/files/infrastructure/FileJSDataverseRepository.ts +++ b/src/files/infrastructure/FileJSDataverseRepository.ts @@ -147,20 +147,4 @@ export class FileJSDataverseRepository implements FileRepository { throw new Error(error.message) }) } - - getOriginalFileById(id: number): Promise { - return fetch(`${FileJSDataverseRepository.DATAVERSE_BACKEND_URL}/api/access/datafile/${id}`) - .then((response) => { - if (!response.ok) { - throw new Error('Network response was not ok') - } - return response.blob() - }) - .then((blob) => { - return URL.createObjectURL(blob) - }) - .catch(() => { - return undefined - }) - } } diff --git a/src/files/infrastructure/mappers/JSFileMapper.ts b/src/files/infrastructure/mappers/JSFileMapper.ts index 3956b6d25..4dc167a41 100644 --- a/src/files/infrastructure/mappers/JSFileMapper.ts +++ b/src/files/infrastructure/mappers/JSFileMapper.ts @@ -46,14 +46,15 @@ export class JSFileMapper { this.toFileDate(jsFile.creationDate, jsFile.publicationDate, jsFile.embargo), this.toFileDownloads(), this.toFileLabels(jsFile.categories, jsFile.tabularTags), - false, // TODO - Implement this when it is added to js-dataverse - { status: FileIngestStatus.NONE }, // TODO - Implement this when it is added to js-dataverse - this.toFileChecksum(jsFile.checksum), + false, + { status: FileIngestStatus.NONE }, + this.toFileOriginalFileDownloadUrl(jsFile.id), this.toFileThumbnail(), this.toFileDirectory(jsFile.directoryLabel), this.toFileEmbargo(jsFile.embargo), this.toFileTabularData(), - this.toFileDescription(jsFile.description) + this.toFileDescription(jsFile.description), + this.toFileChecksum(jsFile.checksum) ) } @@ -159,6 +160,10 @@ export class JSFileMapper { return undefined } + static toFileOriginalFileDownloadUrl(id: number): string { + return `/api/access/datafile/${id}` + } + static toFileThumbnail(): undefined { return undefined // This is always undefined because the thumbnails come from a different endpoint } diff --git a/src/sections/dataset/DatasetFactory.tsx b/src/sections/dataset/DatasetFactory.tsx index e303cc1c0..1ac3e1e15 100644 --- a/src/sections/dataset/DatasetFactory.tsx +++ b/src/sections/dataset/DatasetFactory.tsx @@ -11,7 +11,6 @@ import { SettingJSDataverseRepository } from '../../settings/infrastructure/Sett import { FilePermissionsProvider } from '../file/file-permissions/FilePermissionsProvider' import { SettingsProvider } from '../settings/SettingsProvider' import { DatasetProvider } from './DatasetProvider' -import { FileDownloadHelperProvider } from '../file/file-download-helper/FileDownloadHelperProvider' const datasetRepository = new DatasetJSDataverseRepository() const fileRepository = new FileJSDataverseRepository() @@ -21,17 +20,15 @@ const settingRepository = new SettingJSDataverseRepository() export class DatasetFactory { static create(): ReactElement { return ( - - - - - - - - - - - + + + + + + + + + ) } } diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx index 12a624318..e26049dab 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx @@ -3,7 +3,6 @@ import FileTypeToFriendlyTypeMap from '../../../../../../../../files/domain/mode import { DropdownButtonItem } from '@iqss/dataverse-design-system' import { useDataset } from '../../../../../../DatasetContext' import { useTranslation } from 'react-i18next' -import { useFileDownload } from '../../../../../../../file/file-download-helper/useFileDownload' interface FileNonTabularDownloadOptionsProps { file: File @@ -12,7 +11,6 @@ interface FileNonTabularDownloadOptionsProps { export function FileNonTabularDownloadOptions({ file }: FileNonTabularDownloadOptionsProps) { const { t } = useTranslation('files') const { dataset } = useDataset() - const { originalFile } = useFileDownload(file.id) const originalFileFormatIsKnown = file.type.toDisplayFormat() !== FileTypeToFriendlyTypeMap.unknown @@ -22,7 +20,7 @@ export function FileNonTabularDownloadOptions({ file }: FileNonTabularDownloadOp return ( Promise -} - -export const FileDownloadHelperContext = createContext({ - download: () => Promise.reject('Not implemented') -}) - -export const useFileDownloadHelper = () => useContext(FileDownloadHelperContext) diff --git a/src/sections/file/file-download-helper/FileDownloadHelperProvider.tsx b/src/sections/file/file-download-helper/FileDownloadHelperProvider.tsx deleted file mode 100644 index a2006db8a..000000000 --- a/src/sections/file/file-download-helper/FileDownloadHelperProvider.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { FileRepository } from '../../../files/domain/repositories/FileRepository' -import { PropsWithChildren } from 'react' -import { getFile } from '../../../files/domain/useCases/getFile' -import { FileDownloadHelperContext } from './FileDownloadHelperContext' - -interface FileDownloadProviderProps { - repository: FileRepository -} - -export function FileDownloadHelperProvider({ - repository, - children -}: PropsWithChildren) { - const download = (id: number): Promise => { - return getFile(repository, id) - } - - return ( - - {children} - - ) -} diff --git a/src/sections/file/file-download-helper/useFileDownload.tsx b/src/sections/file/file-download-helper/useFileDownload.tsx deleted file mode 100644 index 22cb7d418..000000000 --- a/src/sections/file/file-download-helper/useFileDownload.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useEffect, useState } from 'react' -import { useFileDownloadHelper } from './FileDownloadHelperContext' - -export function useFileDownload(id: number) { - const { download } = useFileDownloadHelper() - const [originalFile, setOriginalFile] = useState() - - useEffect(() => { - download(id) - .then((downloadedFile) => { - setOriginalFile(downloadedFile) - }) - .catch((error) => { - console.error('There was an error downloading the file', error) - }) - }, [id]) - - return { originalFile } -} diff --git a/src/stories/dataset/dataset-citation/DatasetCitation.stories.tsx b/src/stories/dataset/dataset-citation/DatasetCitation.stories.tsx index dc89d2f59..cd7d511d4 100644 --- a/src/stories/dataset/dataset-citation/DatasetCitation.stories.tsx +++ b/src/stories/dataset/dataset-citation/DatasetCitation.stories.tsx @@ -34,7 +34,6 @@ export const Default: Story = { export const WithThumbnail: Story = { render: () => { const dataset = DatasetMother.createRealistic({ thumbnail: faker.image.imageUrl() }) - console.log(dataset) return (


diff --git a/src/stories/files/FileMockLoadingRepository.ts b/src/stories/files/FileMockLoadingRepository.ts index 318631d86..eff4bd897 100644 --- a/src/stories/files/FileMockLoadingRepository.ts +++ b/src/stories/files/FileMockLoadingRepository.ts @@ -57,13 +57,4 @@ export class FileMockLoadingRepository implements FileRepository { }, 1000) }) } - - // eslint-disable-next-line unused-imports/no-unused-vars - getOriginalFileById(id: number): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(undefined) - }, 1000) - }) - } } diff --git a/src/stories/files/FileMockNoDataRepository.ts b/src/stories/files/FileMockNoDataRepository.ts index 2b8ff7b41..ba7c10a3b 100644 --- a/src/stories/files/FileMockNoDataRepository.ts +++ b/src/stories/files/FileMockNoDataRepository.ts @@ -57,13 +57,4 @@ export class FileMockNoDataRepository implements FileRepository { }, 1000) }) } - - // eslint-disable-next-line unused-imports/no-unused-vars - getOriginalFileById(id: number): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(undefined) - }, 1000) - }) - } } diff --git a/src/stories/files/FileMockNoFiltersRepository.ts b/src/stories/files/FileMockNoFiltersRepository.ts index aab3eb69b..eec689eaa 100644 --- a/src/stories/files/FileMockNoFiltersRepository.ts +++ b/src/stories/files/FileMockNoFiltersRepository.ts @@ -7,7 +7,6 @@ import { FileUserPermissions } from '../../files/domain/models/FileUserPermissio import { FileUserPermissionsMother } from '../../../tests/component/files/domain/models/FileUserPermissionsMother' import { DatasetVersion } from '../../dataset/domain/models/Dataset' import { FileCriteria } from '../../files/domain/models/FileCriteria' -import { FileMother } from '../../../tests/component/files/domain/models/FileMother' export class FileMockNoFiltersRepository implements FileRepository { getAllByDatasetPersistentId( @@ -60,13 +59,4 @@ export class FileMockNoFiltersRepository implements FileRepository { }, 1000) }) } - - // eslint-disable-next-line unused-imports/no-unused-vars - getOriginalFileById(id: number): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(FileMother.createToDownload()) - }, 1000) - }) - } } diff --git a/src/stories/files/FileMockRepository.ts b/src/stories/files/FileMockRepository.ts index 79d27aaed..63e1499fa 100644 --- a/src/stories/files/FileMockRepository.ts +++ b/src/stories/files/FileMockRepository.ts @@ -8,7 +8,6 @@ import { FileUserPermissionsMother } from '../../../tests/component/files/domain import { FileUserPermissions } from '../../files/domain/models/FileUserPermissions' import { DatasetVersion } from '../../dataset/domain/models/Dataset' import { FileCriteria } from '../../files/domain/models/FileCriteria' -import { FileMother } from '../../../tests/component/files/domain/models/FileMother' export class FileMockRepository implements FileRepository { // eslint-disable-next-line unused-imports/no-unused-vars @@ -60,13 +59,4 @@ export class FileMockRepository implements FileRepository { }, 1000) }) } - - // eslint-disable-next-line unused-imports/no-unused-vars - getOriginalFileById(id: number): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(FileMother.createToDownload()) - }, 1000) - }) - } } diff --git a/tests/component/files/domain/models/FileMother.ts b/tests/component/files/domain/models/FileMother.ts index 239d1dcaf..0b9b523ad 100644 --- a/tests/component/files/domain/models/FileMother.ts +++ b/tests/component/files/domain/models/FileMother.ts @@ -124,6 +124,7 @@ export class FileMother { description: valueOrUndefined(faker.lorem.paragraph()), isDeleted: faker.datatype.boolean(), ingest: { status: FileIngestStatus.NONE }, + originalFileDownloadUrl: this.createOriginalFileDownloadUrl(), ...props } @@ -139,16 +140,17 @@ export class FileMother { fileMockedData.labels, fileMockedData.isDeleted, fileMockedData.ingest, - fileMockedData.checksum, + fileMockedData.originalFileDownloadUrl, fileMockedData.thumbnail, fileMockedData.directory, fileMockedData.embargo, fileMockedData.tabularData, - fileMockedData.description + fileMockedData.description, + fileMockedData.checksum ) } - static createToDownload(): string { + static createOriginalFileDownloadUrl(): string { const blob = new Blob(['Name,Age,Location\nJohn,25,New York\nJane,30,San Francisco'], { type: 'text/csv' }) diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx index 0c8d299cb..c8cc64737 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx @@ -17,7 +17,7 @@ describe('FileDownloadOptions', () => { it('renders the download options for a non-tabular file', () => { cy.customMount() - 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', () => { diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx index 1c3babc91..2fad88bc1 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx @@ -10,8 +10,6 @@ import { DatasetLockMother, DatasetMother } from '../../../../../../../../dataset/domain/models/DatasetMother' -import { FileDownloadHelperProvider } from '../../../../../../../../../../src/sections/file/file-download-helper/FileDownloadHelperProvider' -import { FileRepository } from '../../../../../../../../../../src/files/domain/repositories/FileRepository' const fileNonTabular = FileMother.create({ tabularData: undefined, @@ -25,25 +23,27 @@ describe('FileNonTabularDownloadOptions', () => { }) cy.customMount() - 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.originalFileDownloadUrl) }) it('renders the download options for a non-tabular file', () => { cy.customMount() - cy.findByRole('button', { name: 'Plain Text' }) + cy.findByRole('link', { name: 'Plain Text' }) .should('exist') .should('not.have.class', 'disabled') + .should('have.attr', 'href', fileNonTabular.originalFileDownloadUrl) }) it('does not render the download options for a tabular file', () => { const fileTabular = FileMother.createWithTabularData() cy.customMount() - 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', () => { @@ -56,7 +56,7 @@ describe('FileNonTabularDownloadOptions', () => { }) cy.customMount() - 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', () => { @@ -74,20 +74,6 @@ describe('FileNonTabularDownloadOptions', () => { ) - cy.findByRole('button', { name: 'Plain Text' }).should('have.class', 'disabled') - }) - - it('calls the file repository to get the original file', () => { - const fileRepository: FileRepository = {} as FileRepository - const fileToDownload = FileMother.createToDownload() - fileRepository.getOriginalFileById = cy.stub().resolves(fileToDownload) - - cy.customMount( - - - - ) - - cy.wrap(fileRepository.getOriginalFileById).should('be.calledOnceWith', fileNonTabular.id) + cy.findByRole('link', { name: 'Plain Text' }).should('have.class', 'disabled') }) }) diff --git a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts index 9133904e0..47bf0b851 100644 --- a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts @@ -36,36 +36,39 @@ const fileRepository = new FileJSDataverseRepository() const datasetRepository = new DatasetJSDataverseRepository() const dateNow = new Date() dateNow.setHours(2, 0, 0, 0) -const expectedFile = new File( - 1, - { number: 1, publishingStatus: FilePublishingStatus.DRAFT }, - 'blob', - { - restricted: false, - latestVersionRestricted: false, - canBeRequested: false, - requested: false - }, - new FileType('text/plain'), - new FileSize(25, FileSizeUnit.BYTES), - { - type: FileDateType.DEPOSITED, - date: dateNow - }, - 0, - [], - false, - { status: FileIngestStatus.NONE }, - { - algorithm: 'MD5', - value: '0187a54071542738aa47939e8218e5f2' - }, - undefined, - undefined, - undefined, - undefined, - 'This is an example file' -) +const fileData = (id: number) => { + return new File( + id, + { number: 1, publishingStatus: FilePublishingStatus.DRAFT }, + 'blob', + { + restricted: false, + latestVersionRestricted: false, + canBeRequested: false, + requested: false + }, + new FileType('text/plain'), + new FileSize(25, FileSizeUnit.BYTES), + { + type: FileDateType.DEPOSITED, + date: dateNow + }, + 0, + [], + false, + { status: FileIngestStatus.NONE }, + `/api/access/datafile/${id}`, + undefined, + undefined, + undefined, + undefined, + 'This is an example file', + { + algorithm: 'MD5', + value: '0187a54071542738aa47939e8218e5f2' + } + ) +} describe('File JSDataverse Repository', () => { before(() => { @@ -87,6 +90,7 @@ describe('File JSDataverse Repository', () => { .then((files) => { files.forEach((file, index) => { const expectedFileNames = ['blob', 'blob-1', 'blob-2'] + const expectedFile = fileData(file.id) expect(file.name).to.deep.equal(expectedFileNames[index]) expect(file.version).to.deep.equal(expectedFile.version) expect(file.access).to.deep.equal(expectedFile.access) @@ -100,6 +104,7 @@ describe('File JSDataverse Repository', () => { expect(file.embargo).to.deep.equal(expectedFile.embargo) expect(file.tabularData).to.deep.equal(expectedFile.tabularData) expect(file.description).to.deep.equal(expectedFile.description) + expect(file.originalFileDownloadUrl).to.deep.equal(expectedFile.originalFileDownloadUrl) }) }) }) @@ -145,13 +150,13 @@ describe('File JSDataverse Repository', () => { ) ) .then((files) => { - const expectedPublishedFile = expectedFile + const expectedPublishedFile = fileData(files[0].id) expectedPublishedFile.version.publishingStatus = FilePublishingStatus.RELEASED expectedPublishedFile.date.type = FileDateType.PUBLISHED files.forEach((file) => { expect(file.version).to.deep.equal(expectedPublishedFile.version) - cy.compareDate(file.date.date, expectedFile.date.date) + cy.compareDate(file.date.date, fileData(file.id).date.date) }) }) }) @@ -184,7 +189,7 @@ describe('File JSDataverse Repository', () => { ) ) .then((files) => { - const expectedDeaccessionedFile = expectedFile + const expectedDeaccessionedFile = fileData(files[0].id) expectedDeaccessionedFile.version.publishingStatus = FilePublishingStatus.DEACCESSIONED files.forEach((file) => { @@ -735,34 +740,4 @@ describe('File JSDataverse Repository', () => { }) }) }) - - describe('getOriginalFileById', () => { - it('gets the original file by id', async () => { - const file = FileHelper.create('csv', { - description: 'Some description', - categories: ['category'], - tabIngest: 'true' - }) - const dataset = await DatasetHelper.createWithFiles([file]).then((datasetResponse) => - datasetRepository.getByPersistentId(datasetResponse.persistentId) - ) - if (!dataset) throw new Error('Dataset not found') - - await TestsUtils.wait(2500) // wait for the files to be ingested - - const files = await fileRepository.getAllByDatasetPersistentId( - dataset.persistentId, - dataset.version - ) - - await fileRepository - .getOriginalFileById(files[0].id) - .then((originalFile) => { - expect(originalFile).to.not.be.undefined - }) - .catch((error) => { - throw error - }) - }) - }) }) From 09cd2a3e1da1abe300fa5b2b57bc0b714481fd13 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 28 Nov 2023 16:36:18 +0100 Subject: [PATCH 35/58] fix(FileDownloadOptions): don't show the options if user do not have permissions --- .../access-file-menu/FileDownloadOptions.tsx | 7 +++ .../FileDownloadOptions.spec.tsx | 46 +++++++++++++++++-- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.tsx index 61f0e8400..a1b98acf9 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.tsx @@ -4,6 +4,7 @@ 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 @@ -11,6 +12,12 @@ interface FileDownloadOptionsProps { export function FileDownloadOptions({ file }: FileDownloadOptionsProps) { const { t } = useTranslation('files') + const { sessionUserHasFileDownloadPermission } = useFileDownloadPermission(file) + + if (!sessionUserHasFileDownloadPermission) { + return <> + } + return ( <> diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx index c8cc64737..db4ce1522 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx @@ -1,27 +1,67 @@ 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 fileRepository = {} as FileRepository describe('FileDownloadOptions', () => { + beforeEach(() => { + fileRepository.getUserPermissionsById = cy.stub().resolves( + FileUserPermissionsMother.create({ + canDownloadFile: true + }) + ) + }) + it('renders the download options header', () => { - cy.customMount() + cy.customMount( + + + + ) 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( + + + + ) + + cy.findByRole('heading', { name: 'Download Options' }).should('not.exist') + }) + it('renders the download options for a non-tabular file', () => { - cy.customMount() + cy.customMount( + + {' '} + + ) cy.findByRole('link', { name: 'Plain Text' }).should('exist') }) it('renders the download options for a tabular file', () => { - cy.customMount() + cy.customMount( + + + + ) cy.findByRole('button', { name: 'Comma Separated Values (Original File Format)' }).should( 'exist' From d458245ce82a63c67fc1165f18087545f7fcc253 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 28 Nov 2023 17:51:33 +0100 Subject: [PATCH 36/58] feat(IntegrationFileDownload): add tabular files download --- src/files/domain/models/File.ts | 8 +++- .../infrastructure/mappers/JSFileMapper.ts | 9 +++- .../FileNonTabularDownloadOptions.tsx | 3 +- .../FileTabularDownloadOptions.tsx | 10 ++-- .../files/domain/models/FileMother.ts | 10 ++-- .../access-file-menu/AccessFileMenu.spec.tsx | 16 ++++++- .../FileDownloadOptions.spec.tsx | 4 +- .../FileNonTabularDownloadOptions.spec.tsx | 4 +- .../FileTabularDownloadOptions.spec.tsx | 47 ++++++++++--------- .../e2e/sections/dataset/Dataset.spec.tsx | 13 ++++- .../files/FileJSDataverseRepository.spec.ts | 8 +++- 11 files changed, 87 insertions(+), 45 deletions(-) diff --git a/src/files/domain/models/File.ts b/src/files/domain/models/File.ts index c83274844..1be76bc8f 100644 --- a/src/files/domain/models/File.ts +++ b/src/files/domain/models/File.ts @@ -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, @@ -158,7 +164,7 @@ export class File { readonly labels: FileLabel[], public readonly isDeleted: boolean, public readonly ingest: FileIngest, - public readonly originalFileDownloadUrl: string, + public readonly downloadUrls: FileDownloadUrls, public thumbnail?: string, readonly directory?: string, readonly embargo?: FileEmbargo, diff --git a/src/files/infrastructure/mappers/JSFileMapper.ts b/src/files/infrastructure/mappers/JSFileMapper.ts index 4dc167a41..cba999277 100644 --- a/src/files/infrastructure/mappers/JSFileMapper.ts +++ b/src/files/infrastructure/mappers/JSFileMapper.ts @@ -4,6 +4,7 @@ import { FileChecksum, FileDate, FileDateType, + FileDownloadUrls, FileEmbargo, FileIngestStatus, FileLabel, @@ -160,8 +161,12 @@ export class JSFileMapper { return undefined } - static toFileOriginalFileDownloadUrl(id: number): string { - return `/api/access/datafile/${id}` + 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(): undefined { diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx index e26049dab..eb64a832d 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx @@ -20,8 +20,7 @@ export function FileNonTabularDownloadOptions({ file }: FileNonTabularDownloadOp return ( {originalFileFormatIsKnown && ( - {`${file.type.original} (${t( - 'actions.accessFileMenu.downloadOptions.options.original' - )})`} + {`${ + file.type.original + } (${t('actions.accessFileMenu.downloadOptions.options.original')})`} )} - + {t('actions.accessFileMenu.downloadOptions.options.tabular')} {file.type.original !== 'R Data' && ( - + {t('actions.accessFileMenu.downloadOptions.options.RData')} )} diff --git a/tests/component/files/domain/models/FileMother.ts b/tests/component/files/domain/models/FileMother.ts index 0b9b523ad..c06948a17 100644 --- a/tests/component/files/domain/models/FileMother.ts +++ b/tests/component/files/domain/models/FileMother.ts @@ -124,7 +124,11 @@ export class FileMother { description: valueOrUndefined(faker.lorem.paragraph()), isDeleted: faker.datatype.boolean(), ingest: { status: FileIngestStatus.NONE }, - originalFileDownloadUrl: this.createOriginalFileDownloadUrl(), + downloadUrls: { + original: this.createDownloadUrl(), + tabular: this.createDownloadUrl(), + rData: this.createDownloadUrl() + }, ...props } @@ -140,7 +144,7 @@ export class FileMother { fileMockedData.labels, fileMockedData.isDeleted, fileMockedData.ingest, - fileMockedData.originalFileDownloadUrl, + fileMockedData.downloadUrls, fileMockedData.thumbnail, fileMockedData.directory, fileMockedData.embargo, @@ -150,7 +154,7 @@ export class FileMother { ) } - static createOriginalFileDownloadUrl(): string { + static createDownloadUrl(): string { const blob = new Blob(['Name,Age,Location\nJohn,25,New York\nJane,30,San Francisco'], { type: 'text/csv' }) diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/AccessFileMenu.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/AccessFileMenu.spec.tsx index b25c43a4e..39887fd78 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/AccessFileMenu.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/AccessFileMenu.spec.tsx @@ -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() @@ -55,7 +67,9 @@ describe('AccessFileMenu', () => { it('renders the download options header', () => { cy.customMount( - + + + ) diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx index db4ce1522..f4c6c9b44 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx @@ -63,8 +63,6 @@ describe('FileDownloadOptions', () => { ) - cy.findByRole('button', { name: 'Comma Separated Values (Original File Format)' }).should( - 'exist' - ) + cy.findByRole('link', { name: 'Comma Separated Values (Original File Format)' }).should('exist') }) }) diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx index 2fad88bc1..d2207f468 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx @@ -26,7 +26,7 @@ describe('FileNonTabularDownloadOptions', () => { cy.findByRole('link', { name: 'Original File Format' }) .should('exist') .should('not.have.class', 'disabled') - .should('have.attr', 'href', fileNonTabularUnknown.originalFileDownloadUrl) + .should('have.attr', 'href', fileNonTabularUnknown.downloadUrls.original) }) it('renders the download options for a non-tabular file', () => { @@ -35,7 +35,7 @@ describe('FileNonTabularDownloadOptions', () => { cy.findByRole('link', { name: 'Plain Text' }) .should('exist') .should('not.have.class', 'disabled') - .should('have.attr', 'href', fileNonTabular.originalFileDownloadUrl) + .should('have.attr', 'href', fileNonTabular.downloadUrls.original) }) it('does not render the download options for a tabular file', () => { diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileTabularDownloadOptions.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileTabularDownloadOptions.spec.tsx index d54605ab2..e89603eca 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileTabularDownloadOptions.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileTabularDownloadOptions.spec.tsx @@ -1,4 +1,3 @@ -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 { FileIngestStatus, @@ -24,31 +23,35 @@ describe('FileTabularDownloadOptions', () => { it('renders the download options for a tabular file', () => { cy.customMount() - cy.findByRole('button', { name: 'Comma Separated Values (Original File Format)' }).should( - 'exist' - ) - cy.findByRole('button', { name: 'Tab-Delimited' }) + cy.findByRole('link', { name: 'Comma Separated Values (Original File Format)' }) + .should('exist') + .should('have.attr', 'href', fileTabular.downloadUrls.original) + cy.findByRole('link', { name: 'Tab-Delimited' }) + .should('exist') + .should('have.attr', 'href', fileTabular.downloadUrls.tabular) + .should('not.have.class', 'disabled') + cy.findByRole('link', { name: 'R Data' }) .should('exist') .should('not.have.class', 'disabled') - cy.findByRole('button', { name: 'R Data' }).should('exist').should('not.have.class', 'disabled') + .should('have.attr', 'href', fileTabular.downloadUrls.rData) }) it('renders the download options for a tabular file of unknown original type', () => { - cy.customMount() + cy.customMount() - cy.findByRole('button', { name: /(Original File Format)/ }).should('not.exist') - cy.findByRole('button', { name: 'Tab-Delimited' }) + cy.findByRole('link', { name: /(Original File Format)/ }).should('not.exist') + cy.findByRole('link', { name: 'Tab-Delimited' }) .should('exist') .should('not.have.class', 'disabled') - cy.findByRole('button', { name: 'R Data' }).should('exist').should('not.have.class', 'disabled') + cy.findByRole('link', { name: 'R Data' }).should('exist').should('not.have.class', 'disabled') }) it('does not render the download options for a non-tabular file', () => { cy.customMount() - cy.findByRole('button', { name: /(Original File Format)/ }).should('not.exist') - cy.findByRole('button', { name: 'Tab-Delimited' }).should('not.exist') - cy.findByRole('button', { name: 'R Data' }).should('not.exist') + cy.findByRole('link', { name: /(Original File Format)/ }).should('not.exist') + cy.findByRole('link', { name: 'Tab-Delimited' }).should('not.exist') + cy.findByRole('link', { name: 'R Data' }).should('not.exist') }) it('renders the options as disabled when the file ingest is in progress', () => { @@ -59,13 +62,13 @@ describe('FileTabularDownloadOptions', () => { }) cy.customMount() - cy.findByRole('button', { name: 'Comma Separated Values (Original File Format)' }) + cy.findByRole('link', { name: 'Comma Separated Values (Original File Format)' }) .should('exist') .should('have.class', 'disabled') - cy.findByRole('button', { name: 'Tab-Delimited' }) + cy.findByRole('link', { name: 'Tab-Delimited' }) .should('exist') .should('have.class', 'disabled') - cy.findByRole('button', { name: 'R Data' }).should('exist').should('have.class', 'disabled') + cy.findByRole('link', { name: 'R Data' }).should('exist').should('have.class', 'disabled') }) it('renders the options as disabled when the dataset is locked from file download', () => { @@ -83,13 +86,13 @@ describe('FileTabularDownloadOptions', () => { ) - cy.findByRole('button', { name: 'Comma Separated Values (Original File Format)' }) + cy.findByRole('link', { name: 'Comma Separated Values (Original File Format)' }) .should('exist') .should('have.class', 'disabled') - cy.findByRole('button', { name: 'Tab-Delimited' }) + cy.findByRole('link', { name: 'Tab-Delimited' }) .should('exist') .should('have.class', 'disabled') - cy.findByRole('button', { name: 'R Data' }).should('exist').should('have.class', 'disabled') + cy.findByRole('link', { name: 'R Data' }).should('exist').should('have.class', 'disabled') }) it('does not render the RData option if the file type is already R Data', () => { @@ -98,12 +101,12 @@ describe('FileTabularDownloadOptions', () => { }) cy.customMount() - cy.findByRole('button', { name: 'R Data (Original File Format)' }) + cy.findByRole('link', { name: 'R Data (Original File Format)' }) .should('exist') .should('not.have.class', 'disabled') - cy.findByRole('button', { name: 'Tab-Delimited' }) + cy.findByRole('link', { name: 'Tab-Delimited' }) .should('exist') .should('not.have.class', 'disabled') - cy.findByRole('button', { name: 'R Data' }).should('not.exist') + cy.findByRole('link', { name: 'R Data' }).should('not.exist') }) }) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 945094452..96ef1d87b 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -434,9 +434,18 @@ describe('Dataset', () => { cy.findByText('Files').should('exist') cy.findByRole('button', { name: 'Access File' }).should('exist').click() - cy.findByText('Plain Text').should('exist').click() - cy.visit(`/spa/datasets?persistentId=${persistentId}`) + // Workaround for issue where Cypress gets stuck on the download + cy.window() + .document() + .then(function (doc) { + doc.addEventListener('click', () => { + setTimeout(function () { + doc.location.reload() + }, 5000) + }) + cy.findByText('Plain Text').should('exist').click() + }) cy.findByText('1 Downloads').should('exist') }) diff --git a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts index 47bf0b851..e2513adf0 100644 --- a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts @@ -57,7 +57,11 @@ const fileData = (id: number) => { [], false, { status: FileIngestStatus.NONE }, - `/api/access/datafile/${id}`, + { + original: `/api/access/datafile/${id}?format=original`, + tabular: `/api/access/datafile/${id}`, + rData: `/api/access/datafile/${id}?format=RData` + }, undefined, undefined, undefined, @@ -104,7 +108,7 @@ describe('File JSDataverse Repository', () => { expect(file.embargo).to.deep.equal(expectedFile.embargo) expect(file.tabularData).to.deep.equal(expectedFile.tabularData) expect(file.description).to.deep.equal(expectedFile.description) - expect(file.originalFileDownloadUrl).to.deep.equal(expectedFile.originalFileDownloadUrl) + expect(file.downloadUrls).to.deep.equal(expectedFile.downloadUrls) }) }) }) From f3e239737f65f9251f9523348e34eeade1e1bab7 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Tue, 28 Nov 2023 16:37:30 -0500 Subject: [PATCH 37/58] fix: lint errors --- src/sections/dataset/DatasetFactory.tsx | 4 ++-- .../publish-dataset-menu/PublishDatasetMenu.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sections/dataset/DatasetFactory.tsx b/src/sections/dataset/DatasetFactory.tsx index 55184eea5..7cbdecc0b 100644 --- a/src/sections/dataset/DatasetFactory.tsx +++ b/src/sections/dataset/DatasetFactory.tsx @@ -28,8 +28,8 @@ export class DatasetFactory { - - + + diff --git a/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx index 1b4cdb68b..6ea678467 100644 --- a/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx @@ -35,8 +35,8 @@ export function PublishDatasetMenu({ dataset }: PublishDatasetMenuProps) { asButtonGroup variant="secondary" disabled={ - dataset.checkIsLockedFromPublishing(user.persistentId) || - !dataset.hasValidTermsOfAccess || + dataset.checkIsLockedFromPublishing(user.persistentId) || + !dataset.hasValidTermsOfAccess || !dataset.isValid }> {t('datasetActionButtons.publish.publish')} From 3902bb84e31ee6184b32b14c62416f934d3ccf92 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Wed, 29 Nov 2023 10:37:16 +0100 Subject: [PATCH 38/58] fix: upload files button acting as submit --- .../dataset-upload-files-button/DatasetUploadFilesButton.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sections/dataset/dataset-files/dataset-upload-files-button/DatasetUploadFilesButton.tsx b/src/sections/dataset/dataset-files/dataset-upload-files-button/DatasetUploadFilesButton.tsx index f8cfedcb5..995904c6a 100644 --- a/src/sections/dataset/dataset-files/dataset-upload-files-button/DatasetUploadFilesButton.tsx +++ b/src/sections/dataset/dataset-files/dataset-upload-files-button/DatasetUploadFilesButton.tsx @@ -21,6 +21,7 @@ export function DatasetUploadFilesButton() { return ( ) } + +const getFileIdsFromSelection = (fileSelection: FileSelection): number[] => { + return Object.values(fileSelection) + .filter((file): file is File => file !== undefined) + .map((file) => file.id) +} diff --git a/src/sections/file/multiple-file-download/MultipleFileDownloadContext.ts b/src/sections/file/multiple-file-download/MultipleFileDownloadContext.ts new file mode 100644 index 000000000..95c611513 --- /dev/null +++ b/src/sections/file/multiple-file-download/MultipleFileDownloadContext.ts @@ -0,0 +1,15 @@ +import { createContext, useContext } from 'react' +import { FileDownloadMode } from '../../../files/domain/models/File' + +interface MultipleFileDownloadContextProps { + getMultipleFileDownloadUrl: (ids: number[], downloadMode: FileDownloadMode) => string +} + +export const MultipleFileDownloadContext = createContext({ + getMultipleFileDownloadUrl: () => { + console.error('Not implemented') + return '' + } +}) + +export const useMultipleFileDownload = () => useContext(MultipleFileDownloadContext) diff --git a/src/sections/file/multiple-file-download/MultipleFileDownloadProvider.tsx b/src/sections/file/multiple-file-download/MultipleFileDownloadProvider.tsx new file mode 100644 index 000000000..39c485b94 --- /dev/null +++ b/src/sections/file/multiple-file-download/MultipleFileDownloadProvider.tsx @@ -0,0 +1,24 @@ +import { PropsWithChildren } from 'react' +import { FileRepository } from '../../../files/domain/repositories/FileRepository' +import { MultipleFileDownloadContext } from './MultipleFileDownloadContext' +import { FileDownloadMode } from '../../../files/domain/models/File' +import { getMultipleFileDownloadUrl } from '../../../files/domain/useCases/getMultipleFileDownloadUrl' + +interface MultipleFileDownloadProviderProps { + repository: FileRepository +} + +export function MultipleFileDownloadProvider({ + repository, + children +}: PropsWithChildren) { + const getDownloadUrl = (ids: number[], downloadMode: FileDownloadMode) => { + return getMultipleFileDownloadUrl(repository, ids, downloadMode) + } + + return ( + + {children} + + ) +} diff --git a/tests/component/dataset/domain/models/DatasetMother.ts b/tests/component/dataset/domain/models/DatasetMother.ts index 8a255d4be..9579c8b52 100644 --- a/tests/component/dataset/domain/models/DatasetMother.ts +++ b/tests/component/dataset/domain/models/DatasetMother.ts @@ -291,6 +291,10 @@ export class DatasetMother { hasValidTermsOfAccess: faker.datatype.boolean(), isValid: faker.datatype.boolean(), isReleased: faker.datatype.boolean(), + downloadUrls: { + original: this.createDownloadUrl(), + archival: this.createDownloadUrl() + }, thumbnail: undefined, privateUrl: undefined, ...props @@ -308,11 +312,19 @@ export class DatasetMother { dataset.hasValidTermsOfAccess, dataset.isValid, dataset.isReleased, + dataset.downloadUrls, dataset.thumbnail, dataset.privateUrl ).build() } + 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 createAnonymized(): Dataset { return this.create({ citation: diff --git a/tests/component/files/domain/models/FileMother.ts b/tests/component/files/domain/models/FileMother.ts index c06948a17..d78e4ea23 100644 --- a/tests/component/files/domain/models/FileMother.ts +++ b/tests/component/files/domain/models/FileMother.ts @@ -29,11 +29,6 @@ export class FileEmbargoMother { static create(dateAvailable?: Date): FileEmbargo { return new FileEmbargo(dateAvailable ?? faker.date.future()) } - - static createNotActive(): FileEmbargo { - const dateAvailable = faker.date.past() - return new FileEmbargo(dateAvailable) - } } export class FileIngestMother { static create(props?: Partial): FileIngest { diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx index a82b1ea15..84f0aa09f 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx @@ -8,8 +8,11 @@ import { } from '../../../../../../dataset/domain/models/DatasetMother' import { DownloadFilesButton } from '../../../../../../../../src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton' import { FileMother } from '../../../../../../files/domain/models/FileMother' +import { MultipleFileDownloadProvider } from '../../../../../../../../src/sections/file/multiple-file-download/MultipleFileDownloadProvider' +import { FileRepository } from '../../../../../../../../src/files/domain/repositories/FileRepository' const datasetRepository: DatasetRepository = {} as DatasetRepository +const fileRepository = {} as FileRepository describe('DownloadFilesButton', () => { const withDataset = (component: ReactNode, dataset: DatasetModel | undefined) => { datasetRepository.getByPersistentId = cy.stub().resolves(dataset) @@ -125,22 +128,106 @@ describe('DownloadFilesButton', () => { cy.findByText('Select File(s)').should('exist') }) - it('does not show the No Selected Files modal if files are selected', () => { + it('renders the download url for the selected files when some files are selected and there are no tabular files', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() }) - const files = FileMother.createMany(2) + const files = FileMother.createMany(2, { tabularData: undefined }) + const fileSelection = { + 'some-file-id': files[0], + 'some-other-file-id': files[1] + } + fileRepository.getMultipleFileDownloadUrl = cy.stub().returns('https://some-download-url') + cy.mountAuthenticated( + + {withDataset( + , + datasetWithDownloadFilesPermission + )} + + ) + + cy.findByRole('button', { name: 'Download' }).should( + 'have.attr', + 'href', + 'https://some-download-url' + ) + }) + + it('renders the download url for the selected files when some files are selected and there are tabular files', () => { + const datasetWithDownloadFilesPermission = DatasetMother.create({ + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() + }) + const files = FileMother.createMany(2, { + tabularData: { + variablesCount: 2, + observationsCount: 3, + unf: 'some-unf' + } + }) + const fileSelection = { + 'some-file-id': files[0], + 'some-other-file-id': files[1] + } + fileRepository.getMultipleFileDownloadUrl = cy.stub().returns('https://some-download-url') + cy.mountAuthenticated( + + {withDataset( + , + datasetWithDownloadFilesPermission + )} + + ) + + cy.findByRole('button', { name: 'Download' }).click() + cy.findByRole('link', { name: 'Original Format' }).should( + 'have.attr', + 'href', + 'https://some-download-url' + ) + cy.findByRole('link', { name: 'Archival Format (.tab)' }).should( + 'have.attr', + 'href', + 'https://some-download-url' + ) + }) + + it('renders the dataset download url when all the files are selected', () => { + const datasetWithDownloadFilesPermission = DatasetMother.create({ + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + downloadUrls: { + original: 'https://some-download-url', + archival: 'https://some-download-url' + } + }) + const files = FileMother.createMany(2, { + tabularData: { + variablesCount: 2, + observationsCount: 3, + unf: 'some-unf' + } + }) + const fileSelection = { + 'some-file-id': undefined, + 'some-other-file-id': undefined + } cy.mountAuthenticated( withDataset( - , + , datasetWithDownloadFilesPermission ) ) cy.findByRole('button', { name: 'Download' }).click() - cy.findByText('Select File(s)').should('not.exist') + cy.findByRole('link', { name: 'Original Format' }).should( + 'have.attr', + 'href', + 'https://some-download-url' + ) + cy.findByRole('link', { name: 'Archival Format (.tab)' }).should( + 'have.attr', + 'href', + 'https://some-download-url' + ) }) }) From 5b352eeee5a55eb76df0f6c4d59e691795000a78 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Wed, 6 Dec 2023 16:24:01 +0100 Subject: [PATCH 41/58] feat(IntegrationMultipleFileDownload): connect with js-dataverse --- .../infrastructure/mappers/JSDatasetMapper.ts | 21 +++++++++++++++++-- .../FileJSDataverseRepository.ts | 4 ++++ src/sections/dataset/DatasetFactory.tsx | 21 +++++++++++-------- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index 77a8c3031..b5fcf6ef6 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -14,7 +14,8 @@ import { DatasetMetadataFields, DatasetVersion, MetadataBlockName, - PrivateUrl + PrivateUrl, + DatasetDownloadUrls } from '../../domain/models/Dataset' export class JSDatasetMapper { @@ -25,9 +26,14 @@ export class JSDatasetMapper { requestedVersion?: string, privateUrl?: PrivateUrl ): Dataset { + const version = JSDatasetMapper.toVersion( + jsDataset.versionId, + jsDataset.versionInfo, + requestedVersion + ) return new Dataset.Builder( jsDataset.persistentId, - JSDatasetMapper.toVersion(jsDataset.versionId, jsDataset.versionInfo, requestedVersion), + version, citation, JSDatasetMapper.toSummaryFields(jsDataset.metadataBlocks, summaryFieldsNames), jsDataset.license, @@ -50,6 +56,7 @@ export class JSDatasetMapper { true, // TODO Connect with dataset isValid jsDataset.versionInfo.releaseTime !== undefined && !isNaN(jsDataset.versionInfo.releaseTime.getTime()), // TODO Connect with dataset isReleased, + JSDatasetMapper.toDownloadUrls(jsDataset.persistentId, version), undefined, // TODO: get dataset thumbnail from Dataverse https://github.com/IQSS/dataverse-frontend/issues/203 privateUrl ).build() @@ -185,4 +192,14 @@ export class JSDatasetMapper { return extraFields } + + static toDownloadUrls( + jsDatasetPersistentId: string, + version: DatasetVersion + ): DatasetDownloadUrls { + return { + original: `/api/access/dataset/:persistentId/versions/${version.toString()}?persistentId=${jsDatasetPersistentId}&format=original`, + archival: `/api/access/dataset/:persistentId/versions/${version.toString()}?persistentId=${jsDatasetPersistentId}` + } + } } diff --git a/src/files/infrastructure/FileJSDataverseRepository.ts b/src/files/infrastructure/FileJSDataverseRepository.ts index e16c3c004..41c5cbcbf 100644 --- a/src/files/infrastructure/FileJSDataverseRepository.ts +++ b/src/files/infrastructure/FileJSDataverseRepository.ts @@ -147,4 +147,8 @@ export class FileJSDataverseRepository implements FileRepository { throw new Error(error.message) }) } + + getMultipleFileDownloadUrl(ids: number[], downloadMode: string): string { + return `/api/access/datafiles/${ids.join(',')}?format=${downloadMode}` + } } diff --git a/src/sections/dataset/DatasetFactory.tsx b/src/sections/dataset/DatasetFactory.tsx index 1ac3e1e15..9d53752ce 100644 --- a/src/sections/dataset/DatasetFactory.tsx +++ b/src/sections/dataset/DatasetFactory.tsx @@ -11,6 +11,7 @@ import { SettingJSDataverseRepository } from '../../settings/infrastructure/Sett import { FilePermissionsProvider } from '../file/file-permissions/FilePermissionsProvider' import { SettingsProvider } from '../settings/SettingsProvider' import { DatasetProvider } from './DatasetProvider' +import { MultipleFileDownloadProvider } from '../file/multiple-file-download/MultipleFileDownloadProvider' const datasetRepository = new DatasetJSDataverseRepository() const fileRepository = new FileJSDataverseRepository() @@ -20,15 +21,17 @@ const settingRepository = new SettingJSDataverseRepository() export class DatasetFactory { static create(): ReactElement { return ( - - - - - - - - - + + + + + + + + + + + ) } } From c0644b1e0a8c85162a9a293352e68de0e5aec229 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 11 Dec 2023 10:48:14 +0100 Subject: [PATCH 42/58] fix: button acting as anchor --- .../download-files/DownloadFilesButton.tsx | 17 +++++++++-------- .../download-files/DownloadFilesButton.spec.tsx | 8 +++----- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx index c3ffcfab8..8cf0a9b3b 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx @@ -70,14 +70,15 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto return ( <> - + + + setShowNoFilesSelectedModal(false)} diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx index 84f0aa09f..42844cce6 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx @@ -147,11 +147,9 @@ describe('DownloadFilesButton', () => { ) - cy.findByRole('button', { name: 'Download' }).should( - 'have.attr', - 'href', - 'https://some-download-url' - ) + cy.findByRole('button', { name: 'Download' }) + .parent('a') + .should('have.attr', 'href', 'https://some-download-url') }) it('renders the download url for the selected files when some files are selected and there are tabular files', () => { From e9e255e6175bf19f8a209535fbf541fc5fbcf9f5 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 11 Dec 2023 10:57:04 +0100 Subject: [PATCH 43/58] fix: stories mocks --- .../files/FileMockLoadingRepository.ts | 30 ++----------------- src/stories/files/FileMockNoDataRepository.ts | 14 ++------- .../files/FileMockNoFiltersRepository.ts | 30 ++----------------- src/stories/files/FileMockRepository.ts | 6 ++++ 4 files changed, 14 insertions(+), 66 deletions(-) diff --git a/src/stories/files/FileMockLoadingRepository.ts b/src/stories/files/FileMockLoadingRepository.ts index eff4bd897..949ad992c 100644 --- a/src/stories/files/FileMockLoadingRepository.ts +++ b/src/stories/files/FileMockLoadingRepository.ts @@ -1,12 +1,11 @@ import { FileRepository } from '../../files/domain/repositories/FileRepository' import { File } from '../../files/domain/models/File' import { FilesCountInfo } from '../../files/domain/models/FilesCountInfo' -import { FileUserPermissions } from '../../files/domain/models/FileUserPermissions' -import { FileUserPermissionsMother } from '../../../tests/component/files/domain/models/FileUserPermissionsMother' import { DatasetVersion } from '../../dataset/domain/models/Dataset' import { FileCriteria } from '../../files/domain/models/FileCriteria' +import { FileMockRepository } from './FileMockRepository' -export class FileMockLoadingRepository implements FileRepository { +export class FileMockLoadingRepository extends FileMockRepository implements FileRepository { getAllByDatasetPersistentId( // eslint-disable-next-line unused-imports/no-unused-vars datasetPersistentId: string, @@ -26,7 +25,7 @@ export class FileMockLoadingRepository implements FileRepository { // eslint-disable-next-line unused-imports/no-unused-vars datasetVersion: DatasetVersion, // eslint-disable-next-line unused-imports/no-unused-vars - criteria: FileCriteria + criteria?: FileCriteria ): Promise { return new Promise(() => { setTimeout(() => { @@ -34,27 +33,4 @@ export class FileMockLoadingRepository implements FileRepository { }, 1000) }) } - // eslint-disable-next-line unused-imports/no-unused-vars - getUserPermissionsById(id: number): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(FileUserPermissionsMother.create()) - }, 1000) - }) - } - - getFilesTotalDownloadSizeByDatasetPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - datasetPersistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - datasetVersion: DatasetVersion, - // eslint-disable-next-line unused-imports/no-unused-vars - criteria?: FileCriteria - ): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(19900) - }, 1000) - }) - } } diff --git a/src/stories/files/FileMockNoDataRepository.ts b/src/stories/files/FileMockNoDataRepository.ts index ba7c10a3b..51813c757 100644 --- a/src/stories/files/FileMockNoDataRepository.ts +++ b/src/stories/files/FileMockNoDataRepository.ts @@ -2,12 +2,11 @@ import { FileRepository } from '../../files/domain/repositories/FileRepository' import { File } from '../../files/domain/models/File' import { FilesCountInfo } from '../../files/domain/models/FilesCountInfo' import { FilesCountInfoMother } from '../../../tests/component/files/domain/models/FilesCountInfoMother' -import { FileUserPermissions } from '../../files/domain/models/FileUserPermissions' -import { FileUserPermissionsMother } from '../../../tests/component/files/domain/models/FileUserPermissionsMother' import { DatasetVersion } from '../../dataset/domain/models/Dataset' import { FileCriteria } from '../../files/domain/models/FileCriteria' +import { FileMockRepository } from './FileMockRepository' -export class FileMockNoDataRepository implements FileRepository { +export class FileMockNoDataRepository extends FileMockRepository implements FileRepository { getAllByDatasetPersistentId( // eslint-disable-next-line unused-imports/no-unused-vars datasetPersistentId: string, @@ -48,13 +47,4 @@ export class FileMockNoDataRepository implements FileRepository { }, 1000) }) } - - // eslint-disable-next-line unused-imports/no-unused-vars - getUserPermissionsById(id: number): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(FileUserPermissionsMother.create()) - }, 1000) - }) - } } diff --git a/src/stories/files/FileMockNoFiltersRepository.ts b/src/stories/files/FileMockNoFiltersRepository.ts index eec689eaa..2ce08e666 100644 --- a/src/stories/files/FileMockNoFiltersRepository.ts +++ b/src/stories/files/FileMockNoFiltersRepository.ts @@ -3,12 +3,11 @@ import { File } from '../../files/domain/models/File' import { FilesCountInfo } from '../../files/domain/models/FilesCountInfo' import { FilesCountInfoMother } from '../../../tests/component/files/domain/models/FilesCountInfoMother' import { FilesMockData } from './FileMockData' -import { FileUserPermissions } from '../../files/domain/models/FileUserPermissions' -import { FileUserPermissionsMother } from '../../../tests/component/files/domain/models/FileUserPermissionsMother' import { DatasetVersion } from '../../dataset/domain/models/Dataset' import { FileCriteria } from '../../files/domain/models/FileCriteria' +import { FileMockRepository } from './FileMockRepository' -export class FileMockNoFiltersRepository implements FileRepository { +export class FileMockNoFiltersRepository extends FileMockRepository implements FileRepository { getAllByDatasetPersistentId( // eslint-disable-next-line unused-imports/no-unused-vars datasetPersistentId: string, @@ -28,7 +27,7 @@ export class FileMockNoFiltersRepository implements FileRepository { // eslint-disable-next-line unused-imports/no-unused-vars datasetVersion: DatasetVersion, // eslint-disable-next-line unused-imports/no-unused-vars - criteria: FileCriteria + criteria?: FileCriteria ): Promise { return new Promise((resolve) => { setTimeout(() => { @@ -36,27 +35,4 @@ export class FileMockNoFiltersRepository implements FileRepository { }, 1000) }) } - // eslint-disable-next-line unused-imports/no-unused-vars - getUserPermissionsById(id: number): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(FileUserPermissionsMother.create()) - }, 1000) - }) - } - - getFilesTotalDownloadSizeByDatasetPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - datasetPersistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - datasetVersion: DatasetVersion, - // eslint-disable-next-line unused-imports/no-unused-vars - criteria?: FileCriteria - ): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve(19900) - }, 1000) - }) - } } diff --git a/src/stories/files/FileMockRepository.ts b/src/stories/files/FileMockRepository.ts index 63e1499fa..3086827c5 100644 --- a/src/stories/files/FileMockRepository.ts +++ b/src/stories/files/FileMockRepository.ts @@ -8,6 +8,7 @@ import { FileUserPermissionsMother } from '../../../tests/component/files/domain import { FileUserPermissions } from '../../files/domain/models/FileUserPermissions' import { DatasetVersion } from '../../dataset/domain/models/Dataset' import { FileCriteria } from '../../files/domain/models/FileCriteria' +import { FileMother } from '../../../tests/component/files/domain/models/FileMother' export class FileMockRepository implements FileRepository { // eslint-disable-next-line unused-imports/no-unused-vars @@ -59,4 +60,9 @@ export class FileMockRepository implements FileRepository { }, 1000) }) } + + // eslint-disable-next-line unused-imports/no-unused-vars + getMultipleFileDownloadUrl(ids: number[], downloadMode: string): string { + return FileMother.createDownloadUrl() + } } From 6610d27c10777bb948255ec4ea637b883ac5ab96 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 11 Dec 2023 12:43:50 +0100 Subject: [PATCH 44/58] fix: unit tests mocks --- .../access-file-menu/AccessFileMenu.stories.tsx | 2 +- .../file-info/file-info-cell/FileInfoCell.stories.tsx | 2 +- tests/component/files/domain/models/FileMother.ts | 10 +++++++++- .../access-file-menu/AccessFileMenu.spec.tsx | 9 ++++----- .../access-file-menu/FileDownloadOptions.spec.tsx | 8 ++------ .../FileNonTabularDownloadOptions.spec.tsx | 2 +- .../FileTabularDownloadOptions.spec.tsx | 8 ++++---- .../file-info-cell/file-info-data/FileType.spec.tsx | 2 +- 8 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/stories/dataset/dataset-files/files-table/file-actions/file-action-buttons/access-file-menu/AccessFileMenu.stories.tsx b/src/stories/dataset/dataset-files/files-table/file-actions/file-action-buttons/access-file-menu/AccessFileMenu.stories.tsx index a3a80a32d..b016c7722 100644 --- a/src/stories/dataset/dataset-files/files-table/file-actions/file-action-buttons/access-file-menu/AccessFileMenu.stories.tsx +++ b/src/stories/dataset/dataset-files/files-table/file-actions/file-action-buttons/access-file-menu/AccessFileMenu.stories.tsx @@ -25,7 +25,7 @@ export const NonTabularFiles: Story = { } export const TabularFiles: Story = { - render: () => + render: () => } export const Restricted: Story = { diff --git a/src/stories/dataset/dataset-files/files-table/file-info/file-info-cell/FileInfoCell.stories.tsx b/src/stories/dataset/dataset-files/files-table/file-info/file-info-cell/FileInfoCell.stories.tsx index 6f4d8c401..7cd2f550d 100644 --- a/src/stories/dataset/dataset-files/files-table/file-info/file-info-cell/FileInfoCell.stories.tsx +++ b/src/stories/dataset/dataset-files/files-table/file-info/file-info-cell/FileInfoCell.stories.tsx @@ -29,7 +29,7 @@ export const WithEmbargo: Story = { } export const WithTabularData: Story = { - render: () => + render: () => } export const WithDescription: Story = { diff --git a/tests/component/files/domain/models/FileMother.ts b/tests/component/files/domain/models/FileMother.ts index c06948a17..880cbb459 100644 --- a/tests/component/files/domain/models/FileMother.ts +++ b/tests/component/files/domain/models/FileMother.ts @@ -225,7 +225,7 @@ export class FileMother { }) } - static createWithTabularData(props?: Partial): File { + static createTabular(props?: Partial): File { return this.createDefault({ type: new FileType('text/tab-separated-values', 'Comma Separated Values'), tabularData: { @@ -237,6 +237,14 @@ export class FileMother { }) } + static createNonTabular(props?: Partial): File { + return this.createDefault({ + type: new FileType('text/plain'), + tabularData: undefined, + ...props + }) + } + static createWithDescription(): File { return this.createDefault({ description: faker.lorem.paragraph() diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/AccessFileMenu.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/AccessFileMenu.spec.tsx index 39887fd78..460cfab8d 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/AccessFileMenu.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/AccessFileMenu.spec.tsx @@ -65,12 +65,11 @@ describe('AccessFileMenu', () => { }) it('renders the download options header', () => { + const filePublic = FileMother.createWithPublicAccess() cy.customMount( - - - - - + + + ) cy.findByRole('button', { name: 'Access File' }).click() diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx index f4c6c9b44..912c63a18 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.spec.tsx @@ -1,15 +1,11 @@ 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(() => { diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx index d2207f468..2d9cf8c17 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.spec.tsx @@ -39,7 +39,7 @@ describe('FileNonTabularDownloadOptions', () => { }) it('does not render the download options for a tabular file', () => { - const fileTabular = FileMother.createWithTabularData() + const fileTabular = FileMother.createTabular() cy.customMount() cy.findByRole('link', { name: 'Original File Format' }).should('not.exist') diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileTabularDownloadOptions.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileTabularDownloadOptions.spec.tsx index e89603eca..8dad61fb6 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileTabularDownloadOptions.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileTabularDownloadOptions.spec.tsx @@ -15,8 +15,8 @@ const fileNonTabular = FileMother.create({ tabularData: undefined, type: new FileType('text/plain') }) -const fileTabular = FileMother.createWithTabularData() -const fileTabularUnknown = FileMother.createWithTabularData({ +const fileTabular = FileMother.createTabular() +const fileTabularUnknown = FileMother.createTabular({ type: new FileType('text/tab-separated-values', 'Unknown') }) describe('FileTabularDownloadOptions', () => { @@ -55,7 +55,7 @@ describe('FileTabularDownloadOptions', () => { }) it('renders the options as disabled when the file ingest is in progress', () => { - const fileTabularInProgress = FileMother.createWithTabularData({ + const fileTabularInProgress = FileMother.createTabular({ ingest: { status: FileIngestStatus.IN_PROGRESS } @@ -96,7 +96,7 @@ describe('FileTabularDownloadOptions', () => { }) it('does not render the RData option if the file type is already R Data', () => { - const fileTabularRData = FileMother.createWithTabularData({ + const fileTabularRData = FileMother.createTabular({ type: new FileType('text/tab-separated-values', 'R Data') }) cy.customMount() diff --git a/tests/component/sections/dataset/dataset-files/files-table/files-info/file-info-cell/file-info-data/FileType.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/files-info/file-info-cell/file-info-data/FileType.spec.tsx index db176f339..e68ae02c7 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/files-info/file-info-cell/file-info-data/FileType.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/files-info/file-info-cell/file-info-data/FileType.spec.tsx @@ -28,7 +28,7 @@ describe('FileType', () => { }) it('renders the type correctly when is a tabular file', () => { - const file = FileMother.createWithTabularData({ + const file = FileMother.createTabular({ size: new FileSize(123.03932894722, FileSizeUnit.BYTES) }) From 4ff97b1c962d451a2b97c79722035a2fee543d41 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 11 Dec 2023 16:22:43 +0100 Subject: [PATCH 45/58] feat(IntegrationMultipleFileDownload): add hasOneTabularFileAtLeast property to the dataset model --- src/dataset/domain/models/Dataset.ts | 3 +++ .../infrastructure/mappers/JSDatasetMapper.ts | 1 + .../download-files/DownloadFilesButton.tsx | 21 +++++++++---------- .../dataset/domain/models/DatasetMother.ts | 3 +++ .../mappers/JSDatasetMapper.spec.ts | 15 +++++++++++-- .../DownloadFilesButton.spec.tsx | 16 +++++++++----- .../e2e/sections/dataset/Dataset.spec.tsx | 19 +++++++++++++++++ 7 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/dataset/domain/models/Dataset.ts b/src/dataset/domain/models/Dataset.ts index c6514f082..3ea80ba03 100644 --- a/src/dataset/domain/models/Dataset.ts +++ b/src/dataset/domain/models/Dataset.ts @@ -298,6 +298,7 @@ export class Dataset { public readonly permissions: DatasetPermissions, public readonly locks: DatasetLock[], public readonly hasValidTermsOfAccess: boolean, + public readonly hasOneTabularFileAtLeast: boolean, public readonly isValid: boolean, public readonly isReleased: boolean, public readonly downloadUrls: DatasetDownloadUrls, @@ -375,6 +376,7 @@ export class Dataset { public readonly permissions: DatasetPermissions, public readonly locks: DatasetLock[], public readonly hasValidTermsOfAccess: boolean, + public readonly hasOneTabularFileAtLeast: boolean, public readonly isValid: boolean, public readonly isReleased: boolean, public readonly downloadUrls: DatasetDownloadUrls, @@ -492,6 +494,7 @@ export class Dataset { this.permissions, this.locks, this.hasValidTermsOfAccess, + this.hasOneTabularFileAtLeast, this.isValid, this.isReleased, this.downloadUrls, diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index b5fcf6ef6..1a5f1d738 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -53,6 +53,7 @@ export class JSDatasetMapper { }, // TODO Connect with dataset permissions [], // TODO Connect with dataset locks true, // TODO Connect with dataset hasValidTermsOfAccess + true, // TODO Connect with dataset hasOneTabularFileAtLeast true, // TODO Connect with dataset isValid jsDataset.versionInfo.releaseTime !== undefined && !isNaN(jsDataset.versionInfo.releaseTime.getTime()), // TODO Connect with dataset isReleased, diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx index 8cf0a9b3b..00b5770b1 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx @@ -21,30 +21,29 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto const { dataset } = useDataset() const [showNoFilesSelectedModal, setShowNoFilesSelectedModal] = useState(false) const { getMultipleFileDownloadUrl } = useMultipleFileDownload() - - if ( - files.length < MINIMUM_FILES_COUNT_TO_SHOW_DOWNLOAD_FILES_BUTTON || - !dataset?.permissions.canDownloadFiles - ) { - return <> - } - const onClick = (event: MouseEvent) => { if (Object.keys(fileSelection).length === SELECTED_FILES_EMPTY) { event.preventDefault() setShowNoFilesSelectedModal(true) } } - const getDownloadUrl = (downloadMode: FileDownloadMode) => { + const getDownloadUrl = (downloadMode: FileDownloadMode): string => { const allFilesSelected = Object.values(fileSelection).some((file) => file === undefined) if (allFilesSelected) { - return dataset.downloadUrls[downloadMode] + return dataset ? dataset.downloadUrls[downloadMode] : '' } return getMultipleFileDownloadUrl(getFileIdsFromSelection(fileSelection), downloadMode) } - if (files.some((file) => file.isTabularData)) { + if ( + files.length < MINIMUM_FILES_COUNT_TO_SHOW_DOWNLOAD_FILES_BUTTON || + !dataset?.permissions.canDownloadFiles + ) { + return <> + } + + if (dataset.hasOneTabularFileAtLeast) { return ( <> https://doi.org/10.5072/FK2/B4B2MJ, Root, DRAFT VERSION', hasValidTermsOfAccess: true, + hasOneTabularFileAtLeast: true, isReleased: false, isValid: true, privateUrl: undefined, @@ -204,11 +210,16 @@ const expectedDatasetAlternateVersion = { canPublishDataset: true, canUpdateDataset: true }, - thumbnail: undefined + thumbnail: undefined, + downloadUrls: { + original: `/api/access/dataset/:persistentId/versions/0.0?persistentId=doi:10.5072/FK2/B4B2MJ&format=original`, + archival: `/api/access/dataset/:persistentId/versions/0.0?persistentId=doi:10.5072/FK2/B4B2MJ` + } } describe('JS Dataset Mapper', () => { it('maps jsDataset model to the domain Dataset model', () => { const mapped = JSDatasetMapper.toDataset(jsDataset, citation, datasetSummaryFields) + console.log(mapped) expect(expectedDataset).to.deep.equal(mapped) }) it('maps jsDataset model to the domain Dataset model for alternate version', () => { diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx index 42844cce6..7d8974614 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx @@ -74,7 +74,8 @@ describe('DownloadFilesButton', () => { it('renders the Download Files button as a dropdown if there are tabular files in the dataset', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ - permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: true }) const files = FileMother.createMany(2, { tabularData: { @@ -97,7 +98,8 @@ describe('DownloadFilesButton', () => { it('does not render the Download Files button as a dropdown if there are no tabular files in the dataset', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ - permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: false }) const files = FileMother.createMany(2, { tabularData: undefined }) cy.mountAuthenticated( @@ -114,7 +116,8 @@ describe('DownloadFilesButton', () => { it('shows the No Selected Files modal if no files are selected', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ - permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: false }) const files = FileMother.createMany(2, { tabularData: undefined }) cy.mountAuthenticated( @@ -130,7 +133,8 @@ describe('DownloadFilesButton', () => { it('renders the download url for the selected files when some files are selected and there are no tabular files', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ - permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: false }) const files = FileMother.createMany(2, { tabularData: undefined }) const fileSelection = { @@ -154,7 +158,8 @@ describe('DownloadFilesButton', () => { it('renders the download url for the selected files when some files are selected and there are tabular files', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ - permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: true }) const files = FileMother.createMany(2, { tabularData: { @@ -193,6 +198,7 @@ describe('DownloadFilesButton', () => { it('renders the dataset download url when all the files are selected', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: true, downloadUrls: { original: 'https://some-download-url', archival: 'https://some-download-url' diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 96ef1d87b..3a31583fa 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -450,4 +450,23 @@ describe('Dataset', () => { cy.findByText('1 Downloads').should('exist') }) }) + + it.only('downloads multiple files', () => { + cy.wrap( + DatasetHelper.createWithFiles(FileHelper.createMany(3)).then((dataset) => + DatasetHelper.publish(dataset.persistentId) + ) + ) + .its('persistentId') + .then((persistentId: string) => { + cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.wait(1500) // Wait for the page to load + + cy.findByText('Files').should('exist') + + cy.get('table > thead > tr > th > input[type=checkbox]').click() + + cy.findByRole('button', { name: 'Download' }).should('exist').click() + }) + }) }) From d03277715350617215dea32b6453b73b62a8f6a5 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 11 Dec 2023 16:41:58 +0100 Subject: [PATCH 46/58] feat(IntegrationMultipleFileDownload): add e2e test --- .../mappers/JSDatasetMapper.spec.ts | 1 - .../e2e/sections/dataset/Dataset.spec.tsx | 99 +++++++++++-------- 2 files changed, 58 insertions(+), 42 deletions(-) diff --git a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts index f5965039a..1c4d33408 100644 --- a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts +++ b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts @@ -219,7 +219,6 @@ const expectedDatasetAlternateVersion = { describe('JS Dataset Mapper', () => { it('maps jsDataset model to the domain Dataset model', () => { const mapped = JSDatasetMapper.toDataset(jsDataset, citation, datasetSummaryFields) - console.log(mapped) expect(expectedDataset).to.deep.equal(mapped) }) it('maps jsDataset model to the domain Dataset model for alternate version', () => { diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 3a31583fa..96d0cf3c2 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -419,54 +419,71 @@ describe('Dataset', () => { }) }) - it('downloads a file', () => { - cy.wrap( - DatasetHelper.createWithFiles(FileHelper.createMany(1)).then((dataset) => - DatasetHelper.publish(dataset.persistentId) + describe('Downloading files', () => { + it('downloads a file', () => { + cy.wrap( + DatasetHelper.createWithFiles(FileHelper.createMany(1)).then((dataset) => + DatasetHelper.publish(dataset.persistentId) + ) ) - ) - .its('persistentId') - .then((persistentId: string) => { - cy.wait(1500) // Wait for the dataset to be published - cy.visit(`/spa/datasets?persistentId=${persistentId}`) - cy.wait(1500) // Wait for the page to load - - cy.findByText('Files').should('exist') - - cy.findByRole('button', { name: 'Access File' }).should('exist').click() - - // Workaround for issue where Cypress gets stuck on the download - cy.window() - .document() - .then(function (doc) { - doc.addEventListener('click', () => { - setTimeout(function () { - doc.location.reload() - }, 5000) + .its('persistentId') + .then((persistentId: string) => { + cy.wait(1500) // Wait for the dataset to be published + cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.wait(1500) // Wait for the page to load + + cy.findByText('Files').should('exist') + + cy.findByRole('button', { name: 'Access File' }).should('exist').click() + + // Workaround for issue where Cypress gets stuck on the download + cy.window() + .document() + .then(function (doc) { + doc.addEventListener('click', () => { + setTimeout(function () { + doc.location.reload() + }, 5000) + }) + cy.findByText('Plain Text').should('exist').click() }) - cy.findByText('Plain Text').should('exist').click() - }) - cy.findByText('1 Downloads').should('exist') - }) - }) + cy.findByText('1 Downloads').should('exist') + }) + }) - it.only('downloads multiple files', () => { - cy.wrap( - DatasetHelper.createWithFiles(FileHelper.createMany(3)).then((dataset) => - DatasetHelper.publish(dataset.persistentId) + it('downloads multiple files', () => { + cy.wrap( + DatasetHelper.createWithFiles(FileHelper.createMany(3)).then((dataset) => + DatasetHelper.publish(dataset.persistentId) + ) ) - ) - .its('persistentId') - .then((persistentId: string) => { - cy.visit(`/spa/datasets?persistentId=${persistentId}`) - cy.wait(1500) // Wait for the page to load + .its('persistentId') + .then((persistentId: string) => { + cy.wait(1500) // Wait for the page to load + cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.wait(1500) // Wait for the page to load + + cy.findByText('Files').should('exist') - cy.findByText('Files').should('exist') + cy.get('table > thead > tr > th > input[type=checkbox]').click() - cy.get('table > thead > tr > th > input[type=checkbox]').click() + cy.findByRole('button', { name: 'Download' }).should('exist').click({ force: true }) - cy.findByRole('button', { name: 'Download' }).should('exist').click() - }) + // Workaround for issue where Cypress gets stuck on the download + cy.window() + .document() + .then(function (doc) { + doc.addEventListener('click', () => { + setTimeout(function () { + doc.location.reload() + }, 5000) + }) + cy.findByText('Original Format').should('exist').click() + }) + + cy.findAllByText('1 Downloads').should('exist') + }) + }) }) }) From ce4dbec7a0f5a1233c02cb5dc0ca97e165a52d06 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Mon, 11 Dec 2023 16:16:57 -0500 Subject: [PATCH 47/58] Add alias to Cypress query to avoid asynchronous page update errors --- .../e2e/sections/dataset/Dataset.spec.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 46d3f9a3d..c6043455d 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -277,11 +277,14 @@ describe('Dataset', () => { cy.findByText('Restricted File Icon').should('not.exist') cy.findByText('Restricted with access Icon').should('exist') - - cy.findByRole('button', { name: 'Access File' }).should('exist').click() + cy.findByRole('button', { name: 'Access File' }).as('accessButton') + cy.get('@accessButton').should('exist') + cy.get('@accessButton').click() cy.findByText('Restricted with Access Granted').should('exist') - cy.findByRole('button', { name: 'File Options' }).should('exist').click() + cy.findByRole('button', { name: 'File Options' }).as('fileOptions') + cy.get('@fileOptions').should('exist') + cy.get('@fileOptions').click() cy.findByText('Unrestrict').should('exist') }) }) @@ -361,7 +364,9 @@ describe('Dataset', () => { cy.findByText('Edit Files').should('exist') - cy.findByRole('button', { name: 'Access File' }).should('exist').click() + cy.findByRole('button', { name: 'Access File' }).as('accessButton') + cy.get('@accessButton').should('exist') + cy.get('@accessButton').click() cy.findByText('Embargoed').should('exist') }) }) From d0650951cd061f00bcd6cebaaec932305ab0e31d Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Mon, 11 Dec 2023 20:50:40 -0500 Subject: [PATCH 48/58] fix: remove extra parenthesis --- .../access-dataset-menu/AccessDatasetMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx index 9a991b0b2..0f4a7445b 100644 --- a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx @@ -53,7 +53,7 @@ export function AccessDatasetMenu({ handleDownload(FileDownloadSizeMode.ARCHIVAL)}> {t('datasetActionButtons.accessDataset.downloadArchiveZip')} ( - {getFormattedFileSize(FileDownloadSizeMode.ARCHIVAL)}) ) + {getFormattedFileSize(FileDownloadSizeMode.ARCHIVAL)}) ) : ( From 6a44d6f93c2352e10a9aec4188a610c80c2fd24c Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Mon, 11 Dec 2023 20:51:11 -0500 Subject: [PATCH 49/58] fix: add test data for fileDownloadSizes --- .../DatasetActionButtons.stories.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/stories/dataset/dataset-action-buttons/DatasetActionButtons.stories.tsx b/src/stories/dataset/dataset-action-buttons/DatasetActionButtons.stories.tsx index 9226ec599..87570d5de 100644 --- a/src/stories/dataset/dataset-action-buttons/DatasetActionButtons.stories.tsx +++ b/src/stories/dataset/dataset-action-buttons/DatasetActionButtons.stories.tsx @@ -48,6 +48,10 @@ export const WithNoDatasetPermissions: Story = { permissions: DatasetPermissionsMother.createWithNoDatasetPermissions(), version: DatasetVersionMother.createDraftAsLatestVersion(), hasValidTermsOfAccess: true, + fileDownloadSizes: [ + DatasetFileDownloadSizeMother.createOriginal(), + DatasetFileDownloadSizeMother.createArchival() + ], isValid: true, isReleased: true })} @@ -66,6 +70,10 @@ export const WithUpdateAndNoPublishDatasetPermissions: Story = { }), version: DatasetVersionMother.createDraftAsLatestVersion(), hasValidTermsOfAccess: true, + fileDownloadSizes: [ + DatasetFileDownloadSizeMother.createOriginal(), + DatasetFileDownloadSizeMother.createArchival() + ], isValid: true, isReleased: true })} From 7bf8615f22e40c36d9ea0d105d66cd073811c8e8 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 12 Dec 2023 11:52:07 +0100 Subject: [PATCH 50/58] feat(IntegrationDatasetDownload): add href with the downloadUrls --- public/locales/en/dataset.json | 9 +- src/dataset/domain/models/Dataset.ts | 20 +++-- .../infrastructure/mappers/JSDatasetMapper.ts | 5 +- src/files/domain/models/File.ts | 9 +- .../DatasetActionButtons.tsx | 1 + .../access-dataset-menu/AccessDatasetMenu.tsx | 90 ++++++++++--------- .../dataset/domain/models/DatasetMother.ts | 38 ++++++-- .../AccessDatasetMenu.spec.tsx | 22 ++++- 8 files changed, 126 insertions(+), 68 deletions(-) diff --git a/public/locales/en/dataset.json b/public/locales/en/dataset.json index e08244985..f7784bd93 100644 --- a/public/locales/en/dataset.json +++ b/public/locales/en/dataset.json @@ -54,9 +54,12 @@ }, "accessDataset": { "title": "Access Dataset", - "downloadZip": "Download ZIP", - "downloadOriginalZip": "Original Format ZIP", - "downloadArchiveZip": "Archive Format (.tab) ZIP" + "downloadOptions": { + "header": "Download Options", + "zip": "Download ZIP", + "originalZip": "Original Format ZIP", + "archiveZip": "Archive Format (.tab) ZIP" + } }, "uploadFiles": "Upload Files" }, diff --git a/src/dataset/domain/models/Dataset.ts b/src/dataset/domain/models/Dataset.ts index f500394ae..caaaae045 100644 --- a/src/dataset/domain/models/Dataset.ts +++ b/src/dataset/domain/models/Dataset.ts @@ -264,6 +264,11 @@ export interface PrivateUrl { urlSnippet: string } +export interface DatasetDownloadUrls { + original: string + archival: string +} + export class Dataset { constructor( public readonly persistentId: string, @@ -280,9 +285,10 @@ export class Dataset { public readonly hasOneTabularFileAtLeast: boolean, public readonly isValid: boolean, public readonly isReleased: boolean, + public readonly downloadUrls: DatasetDownloadUrls, + public readonly fileDownloadSizes: FileDownloadSize[], public readonly thumbnail?: string, - public readonly privateUrl?: PrivateUrl, - public readonly fileDownloadSizes?: FileDownloadSize[] + public readonly privateUrl?: PrivateUrl ) {} public getTitle(): string { @@ -365,9 +371,10 @@ export class Dataset { public readonly hasOneTabularFileAtLeast: boolean, public readonly isValid: boolean, public readonly isReleased: boolean, + public readonly downloadUrls: DatasetDownloadUrls, + public readonly fileDownloadSizes: FileDownloadSize[], public readonly thumbnail?: string, - public readonly privateUrl?: PrivateUrl, - public readonly fileDownloadSizes?: FileDownloadSize[] + public readonly privateUrl?: PrivateUrl ) { this.withLabels() this.withAlerts() @@ -475,9 +482,10 @@ export class Dataset { this.hasOneTabularFileAtLeast, this.isValid, this.isReleased, + this.downloadUrls, + this.fileDownloadSizes, this.thumbnail, - this.privateUrl, - this.fileDownloadSizes + this.privateUrl ) } } diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index fe5bb7fc0..4ddc86ebb 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -50,9 +50,10 @@ export class JSDatasetMapper { true, // TODO Connect with dataset hasOneTabularFileAtLeast true, // TODO Connect with dataset isValid JSDatasetMapper.toIsReleased(jsDataset.versionInfo), + undefined, + [], // TODO: Connect with file download use case undefined, // TODO: get dataset thumbnail from Dataverse https://github.com/IQSS/dataverse-frontend/issues/203 - privateUrl, - [] // TODO: Connect with file download use case + privateUrl ).build() } diff --git a/src/files/domain/models/File.ts b/src/files/domain/models/File.ts index 7ecc13ecc..cc89d0437 100644 --- a/src/files/domain/models/File.ts +++ b/src/files/domain/models/File.ts @@ -63,17 +63,16 @@ export class FileSize { } } -export enum FileDownloadSizeMode { - ALL = 'All', - ORIGINAL = 'Original', - ARCHIVAL = 'Archival' +export enum FileDownloadMode { + ORIGINAL = 'original', + ARCHIVAL = 'archival' } export class FileDownloadSize extends FileSize { constructor( readonly value: number, readonly unit: FileSizeUnit, - readonly mode: FileDownloadSizeMode + readonly mode: FileDownloadMode ) { super(value, unit) } diff --git a/src/sections/dataset/dataset-action-buttons/DatasetActionButtons.tsx b/src/sections/dataset/dataset-action-buttons/DatasetActionButtons.tsx index 1c5a41116..96260a4a9 100644 --- a/src/sections/dataset/dataset-action-buttons/DatasetActionButtons.tsx +++ b/src/sections/dataset/dataset-action-buttons/DatasetActionButtons.tsx @@ -21,6 +21,7 @@ export function DatasetActionButtons({ dataset }: DatasetActionButtonsProps) { permissions={dataset.permissions} hasOneTabularFileAtLeast={dataset.hasOneTabularFileAtLeast} fileDownloadSizes={dataset.fileDownloadSizes} + downloadUrls={dataset.downloadUrls} /> diff --git a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx index 0f4a7445b..499a62528 100644 --- a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx @@ -1,4 +1,5 @@ import { + DatasetDownloadUrls, DatasetPermissions, DatasetPublishingStatus, DatasetVersion @@ -6,22 +7,26 @@ import { import { DropdownButton, DropdownButtonItem, DropdownHeader } from '@iqss/dataverse-design-system' import { useTranslation } from 'react-i18next' -import { FileDownloadSize, FileDownloadSizeMode } from '../../../../files/domain/models/File' +import { FileDownloadSize, FileDownloadMode } from '../../../../files/domain/models/File' import { Download } from 'react-bootstrap-icons' interface AccessDatasetMenuProps { version: DatasetVersion permissions: DatasetPermissions hasOneTabularFileAtLeast: boolean - fileDownloadSizes?: FileDownloadSize[] + fileDownloadSizes: FileDownloadSize[] + downloadUrls: DatasetDownloadUrls } export function AccessDatasetMenu({ version, permissions, hasOneTabularFileAtLeast, - fileDownloadSizes + fileDownloadSizes, + downloadUrls }: AccessDatasetMenuProps) { + const { t } = useTranslation('dataset') + if ( !permissions.canDownloadFiles || (version.publishingStatus === DatasetPublishingStatus.DEACCESSIONED && @@ -30,41 +35,6 @@ export function AccessDatasetMenu({ return <> } - function getFormattedFileSize(mode: FileDownloadSizeMode): string { - const foundSize = fileDownloadSizes && fileDownloadSizes.find((size) => size.mode === mode) - return foundSize ? foundSize.toString() : '' - } - - const handleDownload = (type: FileDownloadSizeMode) => { - //TODO: implement download feature - console.log('downloading file ' + type) - } - - interface DatasetDownloadOptionsProps { - datasetContainsTabularFiles: boolean - } - - const DatasetDownloadOptions = ({ datasetContainsTabularFiles }: DatasetDownloadOptionsProps) => { - return datasetContainsTabularFiles ? ( - <> - handleDownload(FileDownloadSizeMode.ORIGINAL)}> - {t('datasetActionButtons.accessDataset.downloadOriginalZip')} ( - {getFormattedFileSize(FileDownloadSizeMode.ORIGINAL)}) - - handleDownload(FileDownloadSizeMode.ARCHIVAL)}> - {t('datasetActionButtons.accessDataset.downloadArchiveZip')} ( - {getFormattedFileSize(FileDownloadSizeMode.ARCHIVAL)}) - - - ) : ( - handleDownload(FileDownloadSizeMode.ORIGINAL)}> - {t('datasetActionButtons.accessDataset.downloadZip')} ( - {getFormattedFileSize(FileDownloadSizeMode.ORIGINAL)}) ) - - ) - } - - const { t } = useTranslation('dataset') return ( - Download Options + {t('datasetActionButtons.accessDataset.downloadOptions.header')} - + ) } +interface DatasetDownloadOptionsProps { + hasOneTabularFileAtLeast: boolean + fileDownloadSizes: FileDownloadSize[] + downloadUrls: DatasetDownloadUrls +} + +const DatasetDownloadOptions = ({ + hasOneTabularFileAtLeast, + fileDownloadSizes, + downloadUrls +}: DatasetDownloadOptionsProps) => { + const { t } = useTranslation('dataset') + function getFormattedFileSize(mode: FileDownloadMode): string { + const foundSize = fileDownloadSizes && fileDownloadSizes.find((size) => size.mode === mode) + return foundSize ? foundSize.toString() : '' + } + + return hasOneTabularFileAtLeast ? ( + <> + + {t('datasetActionButtons.accessDataset.downloadOptions.originalZip')} ( + {getFormattedFileSize(FileDownloadMode.ORIGINAL)}) + + + {t('datasetActionButtons.accessDataset.downloadOptions.archiveZip')} ( + {getFormattedFileSize(FileDownloadMode.ARCHIVAL)}) + + + ) : ( + + {t('datasetActionButtons.accessDataset.downloadOptions.zip')} ( + {getFormattedFileSize(FileDownloadMode.ORIGINAL)}) ) + + ) +} + // TODO: add download feature https://github.com/IQSS/dataverse-frontend/issues/63 // TODO: add explore feature // TODO: add compute feature diff --git a/tests/component/dataset/domain/models/DatasetMother.ts b/tests/component/dataset/domain/models/DatasetMother.ts index 460ecfb53..2f325a163 100644 --- a/tests/component/dataset/domain/models/DatasetMother.ts +++ b/tests/component/dataset/domain/models/DatasetMother.ts @@ -2,6 +2,7 @@ import { faker } from '@faker-js/faker' import { ANONYMIZED_FIELD_VALUE, Dataset, + DatasetDownloadUrls, DatasetLabelSemanticMeaning, DatasetLabelValue, DatasetLock, @@ -14,7 +15,7 @@ import { } from '../../../../../src/dataset/domain/models/Dataset' import { FileDownloadSize, - FileDownloadSizeMode, + FileDownloadMode, FileSizeUnit } from '../../../../../src/files/domain/models/File' @@ -199,16 +200,33 @@ export class DatasetFileDownloadSizeMother { return new FileDownloadSize( props?.value ?? faker.datatype.number(), props?.unit ?? faker.helpers.arrayElement(Object.values(FileSizeUnit)), - props?.mode ?? faker.helpers.arrayElement(Object.values(FileDownloadSizeMode)) + props?.mode ?? faker.helpers.arrayElement(Object.values(FileDownloadMode)) ) } static createArchival(): FileDownloadSize { - return this.create({ mode: FileDownloadSizeMode.ARCHIVAL }) + return this.create({ mode: FileDownloadMode.ARCHIVAL }) } static createOriginal(): FileDownloadSize { - return this.create({ mode: FileDownloadSizeMode.ORIGINAL }) + return this.create({ mode: FileDownloadMode.ORIGINAL }) + } +} + +export class DatasetDownloadUrlsMother { + static create(props?: Partial): DatasetDownloadUrls { + return { + original: this.createDownloadUrl(), + archival: this.createDownloadUrl(), + ...props + } + } + + 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) } } @@ -315,9 +333,10 @@ export class DatasetMother { hasOneTabularFileAtLeast: faker.datatype.boolean(), isValid: faker.datatype.boolean(), isReleased: faker.datatype.boolean(), + downloadUrls: DatasetDownloadUrlsMother.create(), thumbnail: undefined, privateUrl: undefined, - fileDownloadSizes: undefined, + fileDownloadSizes: [], ...props } @@ -334,9 +353,10 @@ export class DatasetMother { dataset.hasOneTabularFileAtLeast, dataset.isValid, dataset.isReleased, + dataset.downloadUrls, + dataset.fileDownloadSizes, dataset.thumbnail, - dataset.privateUrl, - dataset.fileDownloadSizes + dataset.privateUrl ).build() } @@ -470,8 +490,8 @@ export class DatasetMother { hasValidTermsOfAccess: true, hasOneTabularFileAtLeast: true, fileDownloadSizes: [ - new FileDownloadSize(21.98, FileSizeUnit.KILOBYTES, FileDownloadSizeMode.ORIGINAL), - new FileDownloadSize(21.98, FileSizeUnit.KILOBYTES, FileDownloadSizeMode.ARCHIVAL) + new FileDownloadSize(21.98, FileSizeUnit.KILOBYTES, FileDownloadMode.ORIGINAL), + new FileDownloadSize(21.98, FileSizeUnit.KILOBYTES, FileDownloadMode.ARCHIVAL) ], isValid: true, ...props diff --git a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx index 3a4a65804..cfa5f5847 100644 --- a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx +++ b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx @@ -1,10 +1,12 @@ import { AccessDatasetMenu } from '../../../../../../src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu' import { + DatasetDownloadUrlsMother, DatasetFileDownloadSizeMother, DatasetPermissionsMother, DatasetVersionMother } from '../../../../dataset/domain/models/DatasetMother' +const downloadUrls = DatasetDownloadUrlsMother.create() describe('AccessDatasetMenu', () => { it('renders the AccessDatasetMenu if the user has download files permissions and the dataset is not deaccessioned', () => { const version = DatasetVersionMother.createReleased() @@ -19,6 +21,7 @@ describe('AccessDatasetMenu', () => { hasOneTabularFileAtLeast={true} version={version} permissions={permissions} + downloadUrls={downloadUrls} /> ) @@ -41,6 +44,7 @@ describe('AccessDatasetMenu', () => { hasOneTabularFileAtLeast={true} version={version} permissions={permissions} + downloadUrls={downloadUrls} /> ) cy.findByRole('button', { name: 'Access Dataset' }).should('exist') @@ -59,6 +63,7 @@ describe('AccessDatasetMenu', () => { hasOneTabularFileAtLeast={true} version={version} permissions={permissions} + downloadUrls={downloadUrls} /> ) cy.findByRole('button', { name: 'Access Dataset' }).should('not.exist') @@ -77,10 +82,12 @@ describe('AccessDatasetMenu', () => { hasOneTabularFileAtLeast={true} version={version} permissions={permissions} + downloadUrls={downloadUrls} /> ) cy.findByRole('button', { name: 'Access Dataset' }).should('not.exist') }) + it('displays one dropdown option if there are no tabular files', () => { const version = DatasetVersionMother.createReleased() const permissions = DatasetPermissionsMother.createWithFilesDownloadAllowed() @@ -91,12 +98,16 @@ describe('AccessDatasetMenu', () => { hasOneTabularFileAtLeast={false} version={version} permissions={permissions} + downloadUrls={downloadUrls} /> ) cy.findByRole('button', { name: 'Access Dataset' }).should('exist') cy.findByRole('button', { name: 'Access Dataset' }).click() - cy.findByText(/Download ZIP \(\d+(\.\d+)? (B|KB|MB|GB|TB|PB)\)/).should('exist') + cy.findByText(/Download ZIP \(\d+(\.\d+)? (B|KB|MB|GB|TB|PB)\)/) + .should('exist') + .should('have.attr', 'href', downloadUrls.original) }) + it('displays two dropdown options if there is at least one', () => { const version = DatasetVersionMother.createReleased() const permissions = DatasetPermissionsMother.createWithFilesDownloadAllowed() @@ -110,11 +121,16 @@ describe('AccessDatasetMenu', () => { hasOneTabularFileAtLeast={true} version={version} permissions={permissions} + downloadUrls={downloadUrls} /> ) cy.findByRole('button', { name: 'Access Dataset' }).should('exist') cy.findByRole('button', { name: 'Access Dataset' }).click() - cy.findByText(/Original Format ZIP \(\d+(\.\d+)? (B|KB|MB|GB|TB|PB)\)/).should('exist') - cy.findByText(/Archive Format \(\.tab\) ZIP \(\d+(\.\d+)? (B|KB|MB|GB|TB|PB)\)/).should('exist') + cy.findByText(/Original Format ZIP \(\d+(\.\d+)? (B|KB|MB|GB|TB|PB)\)/) + .should('exist') + .should('have.attr', 'href', downloadUrls.original) + cy.findByText(/Archive Format \(\.tab\) ZIP \(\d+(\.\d+)? (B|KB|MB|GB|TB|PB)\)/) + .should('exist') + .should('have.attr', 'href', downloadUrls.archival) }) }) From 1886f58f32c45c46d63f5d81fcaaf1402e894090 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 12 Dec 2023 12:08:49 +0100 Subject: [PATCH 51/58] feat(IntegrationDatasetDownload): integrate with dataverse API --- .../infrastructure/mappers/JSDatasetMapper.ts | 22 ++++++++++-- .../mappers/JSDatasetMapper.spec.ts | 12 +++++-- .../e2e/sections/dataset/Dataset.spec.tsx | 34 +++++++++++++++++++ .../DatasetJSDataverseRepository.spec.ts | 7 +++- 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index 4ddc86ebb..707c73bc0 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -19,7 +19,8 @@ import { DatasetPermissions, DatasetLock, DatasetLockReason, - PrivateUrl + PrivateUrl, + DatasetDownloadUrls } from '../../domain/models/Dataset' export class JSDatasetMapper { @@ -32,9 +33,14 @@ export class JSDatasetMapper { requestedVersion?: string, privateUrl?: PrivateUrl ): Dataset { + const version = JSDatasetMapper.toVersion( + jsDataset.versionId, + jsDataset.versionInfo, + requestedVersion + ) return new Dataset.Builder( jsDataset.persistentId, - JSDatasetMapper.toVersion(jsDataset.versionId, jsDataset.versionInfo, requestedVersion), + version, citation, JSDatasetMapper.toSummaryFields(jsDataset.metadataBlocks, summaryFieldsNames), jsDataset.license, @@ -50,7 +56,7 @@ export class JSDatasetMapper { true, // TODO Connect with dataset hasOneTabularFileAtLeast true, // TODO Connect with dataset isValid JSDatasetMapper.toIsReleased(jsDataset.versionInfo), - undefined, + JSDatasetMapper.toDownloadUrls(jsDataset.persistentId, version), [], // TODO: Connect with file download use case undefined, // TODO: get dataset thumbnail from Dataverse https://github.com/IQSS/dataverse-frontend/issues/203 privateUrl @@ -213,4 +219,14 @@ export class JSDatasetMapper { } }) } + + static toDownloadUrls( + jsDatasetPersistentId: string, + version: DatasetVersion + ): DatasetDownloadUrls { + return { + original: `/api/access/dataset/:persistentId/versions/${version.toString()}?persistentId=${jsDatasetPersistentId}&format=original`, + archival: `/api/access/dataset/:persistentId/versions/${version.toString()}?persistentId=${jsDatasetPersistentId}` + } + } } diff --git a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts index 665145c6b..11f7f5e70 100644 --- a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts +++ b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts @@ -147,7 +147,11 @@ const expectedDataset = { isReleased: false, thumbnail: undefined, privateUrl: undefined, - fileDownloadSizes: [] + fileDownloadSizes: [], + downloadUrls: { + original: `/api/access/dataset/:persistentId/versions/0.0?persistentId=doi:10.5072/FK2/B4B2MJ&format=original`, + archival: `/api/access/dataset/:persistentId/versions/0.0?persistentId=doi:10.5072/FK2/B4B2MJ` + } } const expectedDatasetAlternateVersion = { persistentId: 'doi:10.5072/FK2/B4B2MJ', @@ -237,7 +241,11 @@ const expectedDatasetAlternateVersion = { canPublishDataset: true, canUpdateDataset: true }, - thumbnail: undefined + thumbnail: undefined, + downloadUrls: { + original: `/api/access/dataset/:persistentId/versions/0.0?persistentId=doi:10.5072/FK2/B4B2MJ&format=original`, + archival: `/api/access/dataset/:persistentId/versions/0.0?persistentId=doi:10.5072/FK2/B4B2MJ` + } } describe('JS Dataset Mapper', () => { it('maps jsDataset model to the domain Dataset model', () => { diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 46d3f9a3d..19c7e9a8d 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -504,4 +504,38 @@ describe('Dataset', () => { }) }) }) + + it('downloads the dataset', () => { + cy.wrap( + DatasetHelper.createWithFiles(FileHelper.createMany(2)).then((dataset) => + DatasetHelper.publish(dataset.persistentId) + ) + ) + .its('persistentId') + .then((persistentId: string) => { + cy.wait(1500) // Wait for the dataset to be published + cy.visit(`/spa/datasets?persistentId=${persistentId}`) + cy.wait(1500) // Wait for the page to load + + cy.findByText('Files').should('exist') + + cy.findByRole('button', { name: 'Access Dataset' }).should('exist').click({ force: true }) + + // Workaround for issue where Cypress gets stuck on the download + cy.window() + .document() + .then(function (doc) { + doc.addEventListener('click', () => { + setTimeout(function () { + doc.location.reload() + }, 5000) + }) + cy.findByRole('link', { name: /Original Format ZIP/ }) + .should('exist') + .click({ force: true }) + }) + + cy.findAllByText('1 Downloads').should('exist') + }) + }) }) diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index 1aa52c69a..35a82359c 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -90,7 +90,11 @@ const datasetData = (persistentId: string, versionId: number) => { canManageFilesPermissions: true, canDeleteDataset: true }, - locks: [] + locks: [], + downloadUrls: { + original: `/api/access/dataset/:persistentId/versions/:draft?persistentId=${persistentId}&format=original`, + archival: `/api/access/dataset/:persistentId/versions/:draft?persistentId=${persistentId}` + } } } @@ -119,6 +123,7 @@ describe('Dataset JSDataverse Repository', () => { expect(dataset.metadataBlocks[0].fields.citationDate).not.to.exist expect(dataset.permissions).to.deep.equal(datasetExpected.permissions) expect(dataset.locks).to.deep.equal(datasetExpected.locks) + expect(dataset.downloadUrls).to.deep.equal(datasetExpected.downloadUrls) }) }) From 6312255ee8e80de1a0aae695085df4cea929cca5 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 12 Dec 2023 13:08:10 +0100 Subject: [PATCH 52/58] feat(IntegrationDatasetDownload): add datasetFilesTotalDownloadSize integration --- .../infrastructure/mappers/JSDatasetMapper.ts | 42 +++++++++++++---- .../DatasetJSDataverseRepository.ts | 47 ++++++++++++++----- .../mappers/JSDatasetMapper.spec.ts | 35 +++++++++++--- .../DatasetJSDataverseRepository.spec.ts | 12 ++++- 4 files changed, 107 insertions(+), 29 deletions(-) diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index 707c73bc0..0af446ac0 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -1,27 +1,28 @@ import { Dataset as JSDataset, + DatasetLock as JSDatasetLock, DatasetMetadataBlock as JSDatasetMetadataBlock, DatasetMetadataBlocks as JSDatasetMetadataBlocks, DatasetMetadataFields as JSDatasetMetadataFields, - DatasetVersionInfo as JSDatasetVersionInfo, DatasetUserPermissions as JSDatasetPermissions, - DatasetLock as JSDatasetLock + DatasetVersionInfo as JSDatasetVersionInfo } from '@iqss/dataverse-client-javascript' import { DatasetVersionState as JSDatasetVersionState } from '@iqss/dataverse-client-javascript/dist/datasets/domain/models/Dataset' import { Dataset, - DatasetPublishingStatus, + DatasetDownloadUrls, + DatasetLock, + DatasetLockReason, DatasetMetadataBlock, DatasetMetadataBlocks, DatasetMetadataFields, + DatasetPermissions, + DatasetPublishingStatus, DatasetVersion, MetadataBlockName, - DatasetPermissions, - DatasetLock, - DatasetLockReason, - PrivateUrl, - DatasetDownloadUrls + PrivateUrl } from '../../domain/models/Dataset' +import { FileDownloadMode, FileDownloadSize, FileSizeUnit } from '../../../files/domain/models/File' export class JSDatasetMapper { static toDataset( @@ -30,6 +31,8 @@ export class JSDatasetMapper { summaryFieldsNames: string[], jsDatasetPermissions: JSDatasetPermissions, jsDatasetLocks: JSDatasetLock[], + jsDatasetFilesTotalOriginalDownloadSize: number, + jsDatasetFilesTotalArchivalDownloadSize: number, requestedVersion?: string, privateUrl?: PrivateUrl ): Dataset { @@ -57,7 +60,10 @@ export class JSDatasetMapper { true, // TODO Connect with dataset isValid JSDatasetMapper.toIsReleased(jsDataset.versionInfo), JSDatasetMapper.toDownloadUrls(jsDataset.persistentId, version), - [], // TODO: Connect with file download use case + JSDatasetMapper.toFileDownloadSizes( + jsDatasetFilesTotalOriginalDownloadSize, + jsDatasetFilesTotalArchivalDownloadSize + ), undefined, // TODO: get dataset thumbnail from Dataverse https://github.com/IQSS/dataverse-frontend/issues/203 privateUrl ).build() @@ -229,4 +235,22 @@ export class JSDatasetMapper { archival: `/api/access/dataset/:persistentId/versions/${version.toString()}?persistentId=${jsDatasetPersistentId}` } } + + static toFileDownloadSizes( + jsDatasetFilesTotalOriginalDownloadSize: number, + jsDatasetFilesTotalArchivalDownloadSize: number + ): FileDownloadSize[] { + return [ + new FileDownloadSize( + jsDatasetFilesTotalOriginalDownloadSize, + FileSizeUnit.BYTES, + FileDownloadMode.ORIGINAL + ), + new FileDownloadSize( + jsDatasetFilesTotalArchivalDownloadSize, + FileSizeUnit.BYTES, + FileDownloadMode.ARCHIVAL + ) + ] + } } diff --git a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts index 02f05124b..c3773641b 100644 --- a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts +++ b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts @@ -11,12 +11,13 @@ import { getDatasetUserPermissions, ReadError, getDatasetLocks, - DatasetLock as JSDatasetLock + DatasetLock as JSDatasetLock, + getDatasetFilesTotalDownloadSize, + FileDownloadSizeMode } from '@iqss/dataverse-client-javascript' import { JSDatasetMapper } from '../mappers/JSDatasetMapper' const includeDeaccessioned = true - export class DatasetJSDataverseRepository implements DatasetRepository { getByPersistentId( persistentId: string, @@ -31,23 +32,41 @@ export class DatasetJSDataverseRepository implements DatasetRepository { getDatasetSummaryFieldNames.execute(), getDatasetCitation.execute(jsDataset.id, this.versionToVersionId(version)), getDatasetUserPermissions.execute(jsDataset.id), - getDatasetLocks.execute(jsDataset.id) + getDatasetLocks.execute(jsDataset.id), + getDatasetFilesTotalDownloadSize.execute( + persistentId, + this.versionToVersionId(version), + FileDownloadSizeMode.ORIGINAL, + undefined, + includeDeaccessioned + ), + getDatasetFilesTotalDownloadSize.execute( + persistentId, + this.versionToVersionId(version), + FileDownloadSizeMode.ARCHIVAL, + undefined, + includeDeaccessioned + ) ]) ) .then( - ([jsDataset, summaryFieldsNames, citation, jsDatasetPermissions, jsDatasetLocks]: [ - JSDataset, - string[], - string, - JSDatasetPermissions, - JSDatasetLock[] - ]) => + ([ + jsDataset, + summaryFieldsNames, + citation, + jsDatasetPermissions, + jsDatasetLocks, + jsDatasetFilesTotalOriginalDownloadSize, + jsDatasetFilesTotalArchivalDownloadSize + ]: [JSDataset, string[], string, JSDatasetPermissions, JSDatasetLock[], number, number]) => JSDatasetMapper.toDataset( jsDataset, citation, summaryFieldsNames, jsDatasetPermissions, jsDatasetLocks, + jsDatasetFilesTotalOriginalDownloadSize, + jsDatasetFilesTotalArchivalDownloadSize, requestedVersion ) ) @@ -76,10 +95,12 @@ export class DatasetJSDataverseRepository implements DatasetRepository { canManageDatasetPermissions: true, canDeleteDatasetDraft: true, canViewUnpublishedDataset: true - }, - [] + }, // TODO Connect with JS dataset permissions for privateUrl when it is available in js-dataverse + [], // TODO Connect with JS dataset locks for privateUrl when it is available in js-dataverse + 0, // TODO Connect with JS dataset filesTotalDownloadSize for privateUrl when it is available in js-dataverse + 0 // TODO Connect with JS dataset filesTotalDownloadSize for privateUrl when it is available in js-dataverse ) - ) // TODO Connect with JS dataset permissions and getDatasetLocks.execute(privateUrlToken) when it is available in js-dataverse + ) .catch((error: ReadError) => { throw new Error(error.message) }) diff --git a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts index 11f7f5e70..cfee61e23 100644 --- a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts +++ b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts @@ -11,6 +11,11 @@ import { DatasetMetadataBlock } from '@iqss/dataverse-client-javascript/dist/datasets/domain/models/Dataset' import { DatasetLockReason } from '../../../../../src/dataset/domain/models/Dataset' +import { + FileDownloadMode, + FileDownloadSize, + FileSizeUnit +} from '../../../../../src/files/domain/models/File' chai.use(chaiAsPromised) const expect = chai.expect @@ -70,6 +75,8 @@ const jsDatasetLocks: JSDatasetLock[] = [ datasetPersistentId: 'doi:10.5072/FK2/B4B2MJ' } ] +const jsDatasetFilesTotalOriginalDownloadSize = 5 +const jsDatasetFilesTotalArchivalDownloadSize = 7 const expectedDataset = { persistentId: 'doi:10.5072/FK2/B4B2MJ', version: { @@ -147,7 +154,10 @@ const expectedDataset = { isReleased: false, thumbnail: undefined, privateUrl: undefined, - fileDownloadSizes: [], + fileDownloadSizes: [ + new FileDownloadSize(5, FileSizeUnit.BYTES, FileDownloadMode.ORIGINAL), + new FileDownloadSize(7, FileSizeUnit.BYTES, FileDownloadMode.ARCHIVAL) + ], downloadUrls: { original: `/api/access/dataset/:persistentId/versions/0.0?persistentId=doi:10.5072/FK2/B4B2MJ&format=original`, archival: `/api/access/dataset/:persistentId/versions/0.0?persistentId=doi:10.5072/FK2/B4B2MJ` @@ -172,7 +182,10 @@ const expectedDatasetAlternateVersion = { isReleased: false, isValid: true, privateUrl: undefined, - fileDownloadSizes: [], + fileDownloadSizes: [ + new FileDownloadSize(5, FileSizeUnit.BYTES, FileDownloadMode.ORIGINAL), + new FileDownloadSize(7, FileSizeUnit.BYTES, FileDownloadMode.ARCHIVAL) + ], labels: [ { semanticMeaning: 'dataset', value: 'Draft' }, { semanticMeaning: 'warning', value: 'Unpublished' } @@ -254,7 +267,9 @@ describe('JS Dataset Mapper', () => { citation, datasetSummaryFields, jsDatasetPermissions, - jsDatasetLocks + jsDatasetLocks, + jsDatasetFilesTotalOriginalDownloadSize, + jsDatasetFilesTotalArchivalDownloadSize ) expect(expectedDataset).to.deep.equal(mapped) }) @@ -265,6 +280,8 @@ describe('JS Dataset Mapper', () => { datasetSummaryFields, jsDatasetPermissions, jsDatasetLocks, + jsDatasetFilesTotalOriginalDownloadSize, + jsDatasetFilesTotalArchivalDownloadSize, '4.0' ) @@ -306,7 +323,9 @@ describe('JS Dataset Mapper', () => { citation, datasetSummaryFields, jsDatasetPermissions, - jsDatasetLocks + jsDatasetLocks, + jsDatasetFilesTotalOriginalDownloadSize, + jsDatasetFilesTotalArchivalDownloadSize ) ) }) @@ -346,7 +365,9 @@ describe('JS Dataset Mapper', () => { citation, datasetSummaryFields, jsDatasetPermissions, - jsDatasetLocks + jsDatasetLocks, + jsDatasetFilesTotalOriginalDownloadSize, + jsDatasetFilesTotalArchivalDownloadSize ) ) }) @@ -385,7 +406,9 @@ describe('JS Dataset Mapper', () => { citation, datasetSummaryFields, jsDatasetPermissions, - jsDatasetLocks + jsDatasetLocks, + jsDatasetFilesTotalOriginalDownloadSize, + jsDatasetFilesTotalArchivalDownloadSize ) ) }) diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index 35a82359c..4916d056b 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -8,6 +8,11 @@ import { DatasetVersion } from '../../../../src/dataset/domain/models/Dataset' import { DatasetHelper } from '../../shared/datasets/DatasetHelper' +import { + FileDownloadMode, + FileDownloadSize, + FileSizeUnit +} from '../../../../src/files/domain/models/File' chai.use(chaiAsPromised) const expect = chai.expect @@ -94,7 +99,11 @@ const datasetData = (persistentId: string, versionId: number) => { downloadUrls: { original: `/api/access/dataset/:persistentId/versions/:draft?persistentId=${persistentId}&format=original`, archival: `/api/access/dataset/:persistentId/versions/:draft?persistentId=${persistentId}` - } + }, + fileDownloadSizes: [ + new FileDownloadSize(0, FileSizeUnit.BYTES, FileDownloadMode.ORIGINAL), + new FileDownloadSize(0, FileSizeUnit.BYTES, FileDownloadMode.ARCHIVAL) + ] } } @@ -124,6 +133,7 @@ describe('Dataset JSDataverse Repository', () => { expect(dataset.permissions).to.deep.equal(datasetExpected.permissions) expect(dataset.locks).to.deep.equal(datasetExpected.locks) expect(dataset.downloadUrls).to.deep.equal(datasetExpected.downloadUrls) + expect(dataset.fileDownloadSizes).to.deep.equal(datasetExpected.fileDownloadSizes) }) }) From 0e3a49aba371e4da2ffc4eaa39e94318e5779d3e Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 12 Dec 2023 13:52:09 +0100 Subject: [PATCH 53/58] feat(IntegrationDatasetDownload): fix size conversion --- public/locales/en/dataset.json | 2 +- src/files/domain/models/File.ts | 7 ++++--- .../access-dataset-menu/AccessDatasetMenu.tsx | 6 +++--- .../AccessDatasetMenu.stories.tsx | 4 ++++ .../dataset/domain/models/DatasetMother.ts | 8 ++++---- .../AccessDatasetMenu.spec.tsx | 18 ++++++++++++------ .../e2e/sections/dataset/Dataset.spec.tsx | 2 +- 7 files changed, 29 insertions(+), 18 deletions(-) diff --git a/public/locales/en/dataset.json b/public/locales/en/dataset.json index f7784bd93..b9a083d83 100644 --- a/public/locales/en/dataset.json +++ b/public/locales/en/dataset.json @@ -58,7 +58,7 @@ "header": "Download Options", "zip": "Download ZIP", "originalZip": "Original Format ZIP", - "archiveZip": "Archive Format (.tab) ZIP" + "archivalZip": "Archival Format (.tab) ZIP" } }, "uploadFiles": "Upload Files" diff --git a/src/files/domain/models/File.ts b/src/files/domain/models/File.ts index cc89d0437..7120feb86 100644 --- a/src/files/domain/models/File.ts +++ b/src/files/domain/models/File.ts @@ -20,7 +20,7 @@ export class FileSize { } constructor(readonly value: number, readonly unit: FileSizeUnit) { - ;[this.value, this.unit] = this.convertToLargestUnit(value, unit) + ;[this.value, this.unit] = FileSize.convertToLargestUnit(value, unit) } toString(): string { @@ -33,7 +33,7 @@ export class FileSize { return this.value * FileSize.multiplier[this.unit] } - private convertToLargestUnit(value: number, unit: FileSizeUnit): [number, FileSizeUnit] { + static convertToLargestUnit(value: number, unit: FileSizeUnit): [number, FileSizeUnit] { let convertedValue = value let convertedUnit = unit @@ -45,7 +45,7 @@ export class FileSize { return [convertedValue, convertedUnit] } - private getNextUnit(unit: FileSizeUnit): FileSizeUnit { + static getNextUnit(unit: FileSizeUnit): FileSizeUnit { switch (unit) { case FileSizeUnit.BYTES: return FileSizeUnit.KILOBYTES @@ -75,6 +75,7 @@ export class FileDownloadSize extends FileSize { readonly mode: FileDownloadMode ) { super(value, unit) + ;[this.value, this.unit] = FileDownloadSize.convertToLargestUnit(value, unit) } } diff --git a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx index 499a62528..dee590789 100644 --- a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx @@ -66,7 +66,7 @@ const DatasetDownloadOptions = ({ }: DatasetDownloadOptionsProps) => { const { t } = useTranslation('dataset') function getFormattedFileSize(mode: FileDownloadMode): string { - const foundSize = fileDownloadSizes && fileDownloadSizes.find((size) => size.mode === mode) + const foundSize = fileDownloadSizes.find((size) => size.mode === mode) return foundSize ? foundSize.toString() : '' } @@ -77,14 +77,14 @@ const DatasetDownloadOptions = ({ {getFormattedFileSize(FileDownloadMode.ORIGINAL)}) - {t('datasetActionButtons.accessDataset.downloadOptions.archiveZip')} ( + {t('datasetActionButtons.accessDataset.downloadOptions.archivalZip')} ( {getFormattedFileSize(FileDownloadMode.ARCHIVAL)}) ) : ( {t('datasetActionButtons.accessDataset.downloadOptions.zip')} ( - {getFormattedFileSize(FileDownloadMode.ORIGINAL)}) ) + {getFormattedFileSize(FileDownloadMode.ORIGINAL)}) ) } diff --git a/src/stories/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.stories.tsx b/src/stories/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.stories.tsx index 402e9753e..52bfda20c 100644 --- a/src/stories/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.stories.tsx +++ b/src/stories/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.stories.tsx @@ -2,6 +2,7 @@ import { Meta, StoryObj } from '@storybook/react' import { WithI18next } from '../../../WithI18next' import { WithSettings } from '../../../WithSettings' import { + DatasetDownloadUrlsMother, DatasetFileDownloadSizeMother, DatasetPermissionsMother, DatasetVersionMother @@ -28,6 +29,7 @@ export const WithDownloadNotAllowed: Story = { version={DatasetVersionMother.createReleased()} permissions={DatasetPermissionsMother.createWithFilesDownloadNotAllowed()} fileDownloadSizes={[DatasetFileDownloadSizeMother.createOriginal()]} + downloadUrls={DatasetDownloadUrlsMother.create()} /> ) } @@ -38,6 +40,7 @@ export const WithoutTabularFiles: Story = { version={DatasetVersionMother.createReleased()} permissions={DatasetPermissionsMother.createWithAllAllowed()} fileDownloadSizes={[DatasetFileDownloadSizeMother.createOriginal()]} + downloadUrls={DatasetDownloadUrlsMother.create()} /> ) } @@ -51,6 +54,7 @@ export const WithTabularFiles: Story = { DatasetFileDownloadSizeMother.createArchival(), DatasetFileDownloadSizeMother.createOriginal() ]} + downloadUrls={DatasetDownloadUrlsMother.create()} /> ) } diff --git a/tests/component/dataset/domain/models/DatasetMother.ts b/tests/component/dataset/domain/models/DatasetMother.ts index 2f325a163..624c02227 100644 --- a/tests/component/dataset/domain/models/DatasetMother.ts +++ b/tests/component/dataset/domain/models/DatasetMother.ts @@ -204,12 +204,12 @@ export class DatasetFileDownloadSizeMother { ) } - static createArchival(): FileDownloadSize { - return this.create({ mode: FileDownloadMode.ARCHIVAL }) + static createArchival(props?: Partial): FileDownloadSize { + return this.create({ mode: FileDownloadMode.ARCHIVAL, ...props }) } - static createOriginal(): FileDownloadSize { - return this.create({ mode: FileDownloadMode.ORIGINAL }) + static createOriginal(props?: Partial): FileDownloadSize { + return this.create({ mode: FileDownloadMode.ORIGINAL, ...props }) } } diff --git a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx index cfa5f5847..352a6174d 100644 --- a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx +++ b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx @@ -5,6 +5,7 @@ import { DatasetPermissionsMother, DatasetVersionMother } from '../../../../dataset/domain/models/DatasetMother' +import { FileSizeUnit } from '../../../../../../src/files/domain/models/File' const downloadUrls = DatasetDownloadUrlsMother.create() describe('AccessDatasetMenu', () => { @@ -91,7 +92,9 @@ describe('AccessDatasetMenu', () => { it('displays one dropdown option if there are no tabular files', () => { const version = DatasetVersionMother.createReleased() const permissions = DatasetPermissionsMother.createWithFilesDownloadAllowed() - const fileDownloadSizes = [DatasetFileDownloadSizeMother.createOriginal()] + const fileDownloadSizes = [ + DatasetFileDownloadSizeMother.createOriginal({ value: 2000, unit: FileSizeUnit.BYTES }) + ] cy.customMount( { ) cy.findByRole('button', { name: 'Access Dataset' }).should('exist') cy.findByRole('button', { name: 'Access Dataset' }).click() - cy.findByText(/Download ZIP \(\d+(\.\d+)? (B|KB|MB|GB|TB|PB)\)/) + cy.findByText('Download ZIP (2 KB)') .should('exist') .should('have.attr', 'href', downloadUrls.original) }) @@ -112,8 +115,11 @@ describe('AccessDatasetMenu', () => { const version = DatasetVersionMother.createReleased() const permissions = DatasetPermissionsMother.createWithFilesDownloadAllowed() const fileDownloadSizes = [ - DatasetFileDownloadSizeMother.createOriginal(), - DatasetFileDownloadSizeMother.createArchival() + DatasetFileDownloadSizeMother.createOriginal({ value: 2000, unit: FileSizeUnit.BYTES }), + DatasetFileDownloadSizeMother.createArchival({ + value: 43483094340394, + unit: FileSizeUnit.BYTES + }) ] cy.customMount( { ) cy.findByRole('button', { name: 'Access Dataset' }).should('exist') cy.findByRole('button', { name: 'Access Dataset' }).click() - cy.findByText(/Original Format ZIP \(\d+(\.\d+)? (B|KB|MB|GB|TB|PB)\)/) + cy.findByText('Original Format ZIP (2 KB)') .should('exist') .should('have.attr', 'href', downloadUrls.original) - cy.findByText(/Archive Format \(\.tab\) ZIP \(\d+(\.\d+)? (B|KB|MB|GB|TB|PB)\)/) + cy.findByText('Archival Format (.tab) ZIP (39.5 TB)') .should('exist') .should('have.attr', 'href', downloadUrls.archival) }) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 19c7e9a8d..3fb4dd9ae 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -367,7 +367,7 @@ describe('Dataset', () => { }) }) - it.only('applies filters to the Files Table in the correct order', () => { + it('applies filters to the Files Table in the correct order', () => { const files = [ FileHelper.create('csv', { description: 'Some description', From 0cf51765ac1c07469ae1c53edde5030b3f502858 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Tue, 12 Dec 2023 08:05:49 -0500 Subject: [PATCH 54/58] fix: Add tests to AccessDatasetMenu.spec.tsx --- .../AccessDatasetMenu.spec.tsx | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx index 3a4a65804..f085f51d2 100644 --- a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx +++ b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx @@ -6,6 +6,54 @@ import { } from '../../../../dataset/domain/models/DatasetMother' describe('AccessDatasetMenu', () => { + it('returns the correct file size string', () => { + const version = DatasetVersionMother.createReleased() + const permissions = DatasetPermissionsMother.createWithFilesDownloadAllowed() + const originalFileDownloadSize = DatasetFileDownloadSizeMother.createOriginal() + const archivalDownloadSize = DatasetFileDownloadSizeMother.createArchival() + const fileDownloadSizes = [originalFileDownloadSize, archivalDownloadSize] + cy.customMount( + + ) + cy.findByRole('button', { name: 'Access Dataset' }).should('exist') + cy.findByRole('button', { name: 'Access Dataset' }).click() + + cy.contains(originalFileDownloadSize.value.toString()).should('be.visible') + cy.contains(archivalDownloadSize.value.toString()).should('be.visible') + cy.contains(originalFileDownloadSize.unit.toString()).should('be.visible') + cy.contains(archivalDownloadSize.unit.toString()).should('be.visible') + }) + + it('logs the correct message when handleDownload is called', () => { + const version = DatasetVersionMother.createReleased() + const permissions = DatasetPermissionsMother.createWithFilesDownloadAllowed() + const fileDownloadSizes = [ + DatasetFileDownloadSizeMother.createOriginal(), + DatasetFileDownloadSizeMother.createArchival() + ] + cy.customMount( + + ) + const consoleLogStub = cy.stub(console, 'log').as('consoleLog') + cy.findByRole('button', { name: 'Access Dataset' }).should('exist') + cy.findByRole('button', { name: 'Access Dataset' }).click() + cy.contains('Original Format ZIP').click() + cy.get('@consoleLog').should('have.been.calledWith', 'downloading file Original') + cy.findByRole('button', { name: 'Access Dataset' }).click() + cy.contains('Archive Format').click() + cy.get('@consoleLog').should('have.been.calledWith', 'downloading file Archival') + }) + it('renders the AccessDatasetMenu if the user has download files permissions and the dataset is not deaccessioned', () => { const version = DatasetVersionMother.createReleased() const permissions = DatasetPermissionsMother.createWithFilesDownloadAllowed() From 249a61993998994f8a5c3f243d1df265590ef4cf Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Tue, 12 Dec 2023 09:19:42 -0500 Subject: [PATCH 55/58] fix: remove extra parenthesis --- .../access-dataset-menu/AccessDatasetMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx index 0f4a7445b..23a52d5a5 100644 --- a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx @@ -59,7 +59,7 @@ export function AccessDatasetMenu({ ) : ( handleDownload(FileDownloadSizeMode.ORIGINAL)}> {t('datasetActionButtons.accessDataset.downloadZip')} ( - {getFormattedFileSize(FileDownloadSizeMode.ORIGINAL)}) ) + {getFormattedFileSize(FileDownloadSizeMode.ORIGINAL)}) ) } From c9cb3feb48f35869c3748aa7bb957a84f4389468 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Thu, 14 Dec 2023 17:12:04 +0100 Subject: [PATCH 56/58] fix: increase zip download limit to not show the msg in beta --- src/settings/infrastructure/SettingJSDataverseRepository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/infrastructure/SettingJSDataverseRepository.ts b/src/settings/infrastructure/SettingJSDataverseRepository.ts index e55e995fe..daeb13e14 100644 --- a/src/settings/infrastructure/SettingJSDataverseRepository.ts +++ b/src/settings/infrastructure/SettingJSDataverseRepository.ts @@ -20,7 +20,7 @@ function mockedSettingResponse(name: SettingName): Setting { case SettingName.ZIP_DOWNLOAD_LIMIT: return { name: SettingName.ZIP_DOWNLOAD_LIMIT, - value: new ZipDownloadLimit(1, FileSizeUnit.BYTES) + value: new ZipDownloadLimit(1, FileSizeUnit.TERABYTES) } as Setting case SettingName.ALLOWED_EXTERNAL_STATUSES: return { From 66f233153fe16d6ef2796d90c1d5b7b887ccbe4c Mon Sep 17 00:00:00 2001 From: MellyGray Date: Thu, 14 Dec 2023 18:05:57 +0100 Subject: [PATCH 57/58] feat(IntegrationMultipleFileDownload): do not zip if only 1 file is selected --- src/files/domain/models/File.ts | 8 +-- .../domain/repositories/FileRepository.ts | 5 +- .../useCases/getMultipleFileDownloadUrl.ts | 5 ++ .../FileJSDataverseRepository.ts | 19 ++++-- .../access-dataset-menu/AccessDatasetMenu.tsx | 18 +++--- .../download-files/DownloadFilesButton.tsx | 3 +- src/stories/files/FileMockRepository.ts | 9 ++- .../dataset/domain/models/DatasetMother.ts | 12 ++-- .../AccessDatasetMenu.spec.tsx | 4 +- .../DownloadFilesButton.spec.tsx | 60 ++++++++++++++++--- .../e2e/sections/dataset/Dataset.spec.tsx | 2 +- 11 files changed, 100 insertions(+), 45 deletions(-) diff --git a/src/files/domain/models/File.ts b/src/files/domain/models/File.ts index 173ae1e0f..ad09ce9f1 100644 --- a/src/files/domain/models/File.ts +++ b/src/files/domain/models/File.ts @@ -63,17 +63,11 @@ export class FileSize { } } -export enum FileDownloadSizeMode { - ALL = 'All', - ORIGINAL = 'Original', - ARCHIVAL = 'Archival' -} - export class FileDownloadSize extends FileSize { constructor( readonly value: number, readonly unit: FileSizeUnit, - readonly mode: FileDownloadSizeMode + readonly mode: FileDownloadMode ) { super(value, unit) } diff --git a/src/files/domain/repositories/FileRepository.ts b/src/files/domain/repositories/FileRepository.ts index 2be3fcd19..9b861084d 100644 --- a/src/files/domain/repositories/FileRepository.ts +++ b/src/files/domain/repositories/FileRepository.ts @@ -1,4 +1,4 @@ -import { File } from '../models/File' +import { File, FileDownloadMode } from '../models/File' import { FileCriteria } from '../models/FileCriteria' import { FilesCountInfo } from '../models/FilesCountInfo' import { FilePaginationInfo } from '../models/FilePaginationInfo' @@ -23,5 +23,6 @@ export interface FileRepository { criteria?: FileCriteria ) => Promise getUserPermissionsById: (id: number) => Promise - getMultipleFileDownloadUrl: (ids: number[], downloadMode: string) => string + getMultipleFileDownloadUrl: (ids: number[], downloadMode: FileDownloadMode) => string + getFileDownloadUrl: (id: number, downloadMode: FileDownloadMode) => string } diff --git a/src/files/domain/useCases/getMultipleFileDownloadUrl.ts b/src/files/domain/useCases/getMultipleFileDownloadUrl.ts index ae6e8015f..fac6afcf3 100644 --- a/src/files/domain/useCases/getMultipleFileDownloadUrl.ts +++ b/src/files/domain/useCases/getMultipleFileDownloadUrl.ts @@ -1,10 +1,15 @@ import { FileRepository } from '../repositories/FileRepository' import { FileDownloadMode } from '../models/File' +const ONLY_ONE_FILE = 1 export function getMultipleFileDownloadUrl( fileRepository: FileRepository, ids: number[], downloadMode: FileDownloadMode ): string { + if (ids.length === ONLY_ONE_FILE) { + return fileRepository.getFileDownloadUrl(ids[0], downloadMode) + } + return fileRepository.getMultipleFileDownloadUrl(ids, downloadMode) } diff --git a/src/files/infrastructure/FileJSDataverseRepository.ts b/src/files/infrastructure/FileJSDataverseRepository.ts index 54b8bb285..96affa553 100644 --- a/src/files/infrastructure/FileJSDataverseRepository.ts +++ b/src/files/infrastructure/FileJSDataverseRepository.ts @@ -1,19 +1,19 @@ import { FileRepository } from '../domain/repositories/FileRepository' -import { File } from '../domain/models/File' +import { File, FileDownloadMode } from '../domain/models/File' import { FilesCountInfo } from '../domain/models/FilesCountInfo' import { FilePaginationInfo } from '../domain/models/FilePaginationInfo' import { FileUserPermissions } from '../domain/models/FileUserPermissions' import { + File as JSFile, + FileDataTable as JSFileTabularData, FileDownloadSizeMode, getDatasetFileCounts, getDatasetFiles, getDatasetFilesTotalDownloadSize, + getFileDataTables, getFileDownloadCount, getFileUserPermissions, - ReadError, - File as JSFile, - getFileDataTables, - FileDataTable as JSFileTabularData + ReadError } from '@iqss/dataverse-client-javascript' import { FileCriteria } from '../domain/models/FileCriteria' import { DomainFileMapper } from './mappers/DomainFileMapper' @@ -156,7 +156,14 @@ export class FileJSDataverseRepository implements FileRepository { }) } - getMultipleFileDownloadUrl(ids: number[], downloadMode: string): string { + getMultipleFileDownloadUrl(ids: number[], downloadMode: FileDownloadMode): string { return `/api/access/datafiles/${ids.join(',')}?format=${downloadMode}` } + + getFileDownloadUrl(id: number, downloadMode: FileDownloadMode): string { + if (downloadMode === FileDownloadMode.ORIGINAL) { + return `/api/access/datafile/${id}?format=${downloadMode}` + } + return `/api/access/datafile/${id}` + } } diff --git a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx index 23a52d5a5..1b3591596 100644 --- a/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.tsx @@ -6,7 +6,7 @@ import { import { DropdownButton, DropdownButtonItem, DropdownHeader } from '@iqss/dataverse-design-system' import { useTranslation } from 'react-i18next' -import { FileDownloadSize, FileDownloadSizeMode } from '../../../../files/domain/models/File' +import { FileDownloadMode, FileDownloadSize } from '../../../../files/domain/models/File' import { Download } from 'react-bootstrap-icons' interface AccessDatasetMenuProps { @@ -30,12 +30,12 @@ export function AccessDatasetMenu({ return <> } - function getFormattedFileSize(mode: FileDownloadSizeMode): string { + function getFormattedFileSize(mode: FileDownloadMode): string { const foundSize = fileDownloadSizes && fileDownloadSizes.find((size) => size.mode === mode) return foundSize ? foundSize.toString() : '' } - const handleDownload = (type: FileDownloadSizeMode) => { + const handleDownload = (type: FileDownloadMode) => { //TODO: implement download feature console.log('downloading file ' + type) } @@ -47,19 +47,19 @@ export function AccessDatasetMenu({ const DatasetDownloadOptions = ({ datasetContainsTabularFiles }: DatasetDownloadOptionsProps) => { return datasetContainsTabularFiles ? ( <> - handleDownload(FileDownloadSizeMode.ORIGINAL)}> + handleDownload(FileDownloadMode.ORIGINAL)}> {t('datasetActionButtons.accessDataset.downloadOriginalZip')} ( - {getFormattedFileSize(FileDownloadSizeMode.ORIGINAL)}) + {getFormattedFileSize(FileDownloadMode.ORIGINAL)}) - handleDownload(FileDownloadSizeMode.ARCHIVAL)}> + handleDownload(FileDownloadMode.ARCHIVAL)}> {t('datasetActionButtons.accessDataset.downloadArchiveZip')} ( - {getFormattedFileSize(FileDownloadSizeMode.ARCHIVAL)}) + {getFormattedFileSize(FileDownloadMode.ARCHIVAL)}) ) : ( - handleDownload(FileDownloadSizeMode.ORIGINAL)}> + handleDownload(FileDownloadMode.ORIGINAL)}> {t('datasetActionButtons.accessDataset.downloadZip')} ( - {getFormattedFileSize(FileDownloadSizeMode.ORIGINAL)}) + {getFormattedFileSize(FileDownloadMode.ORIGINAL)}) ) } diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx index 83a547672..67e1596ff 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.tsx @@ -22,8 +22,9 @@ export function DownloadFilesButton({ files, fileSelection }: DownloadFilesButto const { dataset } = useDataset() const [showNoFilesSelectedModal, setShowNoFilesSelectedModal] = useState(false) const { getMultipleFileDownloadUrl } = useMultipleFileDownload() + const fileSelectionCount = Object.keys(fileSelection).length const onClick = (event: MouseEvent) => { - if (Object.keys(fileSelection).length === SELECTED_FILES_EMPTY) { + if (fileSelectionCount === SELECTED_FILES_EMPTY) { event.preventDefault() setShowNoFilesSelectedModal(true) } diff --git a/src/stories/files/FileMockRepository.ts b/src/stories/files/FileMockRepository.ts index 3086827c5..317facbc4 100644 --- a/src/stories/files/FileMockRepository.ts +++ b/src/stories/files/FileMockRepository.ts @@ -1,6 +1,6 @@ import { FileRepository } from '../../files/domain/repositories/FileRepository' import { FilesMockData } from './FileMockData' -import { File } from '../../files/domain/models/File' +import { File, FileDownloadMode } from '../../files/domain/models/File' import { FilesCountInfo } from '../../files/domain/models/FilesCountInfo' import { FilesCountInfoMother } from '../../../tests/component/files/domain/models/FilesCountInfoMother' import { FilePaginationInfo } from '../../files/domain/models/FilePaginationInfo' @@ -62,7 +62,12 @@ export class FileMockRepository implements FileRepository { } // eslint-disable-next-line unused-imports/no-unused-vars - getMultipleFileDownloadUrl(ids: number[], downloadMode: string): string { + getMultipleFileDownloadUrl(ids: number[], downloadMode: FileDownloadMode): string { + return FileMother.createDownloadUrl() + } + + // eslint-disable-next-line unused-imports/no-unused-vars + getFileDownloadUrl(id: number, downloadMode: FileDownloadMode): string { return FileMother.createDownloadUrl() } } diff --git a/tests/component/dataset/domain/models/DatasetMother.ts b/tests/component/dataset/domain/models/DatasetMother.ts index 09099953d..73d7b18f7 100644 --- a/tests/component/dataset/domain/models/DatasetMother.ts +++ b/tests/component/dataset/domain/models/DatasetMother.ts @@ -13,8 +13,8 @@ import { MetadataBlockName } from '../../../../../src/dataset/domain/models/Dataset' import { + FileDownloadMode, FileDownloadSize, - FileDownloadSizeMode, FileSizeUnit } from '../../../../../src/files/domain/models/File' @@ -199,16 +199,16 @@ export class DatasetFileDownloadSizeMother { return new FileDownloadSize( props?.value ?? faker.datatype.number(), props?.unit ?? faker.helpers.arrayElement(Object.values(FileSizeUnit)), - props?.mode ?? faker.helpers.arrayElement(Object.values(FileDownloadSizeMode)) + props?.mode ?? faker.helpers.arrayElement(Object.values(FileDownloadMode)) ) } static createArchival(): FileDownloadSize { - return this.create({ mode: FileDownloadSizeMode.ARCHIVAL }) + return this.create({ mode: FileDownloadMode.ARCHIVAL }) } static createOriginal(): FileDownloadSize { - return this.create({ mode: FileDownloadSizeMode.ORIGINAL }) + return this.create({ mode: FileDownloadMode.ORIGINAL }) } } @@ -482,8 +482,8 @@ export class DatasetMother { hasValidTermsOfAccess: true, hasOneTabularFileAtLeast: true, fileDownloadSizes: [ - new FileDownloadSize(21.98, FileSizeUnit.KILOBYTES, FileDownloadSizeMode.ORIGINAL), - new FileDownloadSize(21.98, FileSizeUnit.KILOBYTES, FileDownloadSizeMode.ARCHIVAL) + new FileDownloadSize(21.98, FileSizeUnit.KILOBYTES, FileDownloadMode.ORIGINAL), + new FileDownloadSize(21.98, FileSizeUnit.KILOBYTES, FileDownloadMode.ARCHIVAL) ], isValid: true, ...props diff --git a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx index f085f51d2..9a3ff1e0d 100644 --- a/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx +++ b/tests/component/sections/dataset/dataset-action-buttons/access-dataset-menu/AccessDatasetMenu.spec.tsx @@ -48,10 +48,10 @@ describe('AccessDatasetMenu', () => { cy.findByRole('button', { name: 'Access Dataset' }).should('exist') cy.findByRole('button', { name: 'Access Dataset' }).click() cy.contains('Original Format ZIP').click() - cy.get('@consoleLog').should('have.been.calledWith', 'downloading file Original') + cy.get('@consoleLog').should('have.been.calledWith', 'downloading file original') cy.findByRole('button', { name: 'Access Dataset' }).click() cy.contains('Archive Format').click() - cy.get('@consoleLog').should('have.been.calledWith', 'downloading file Archival') + cy.get('@consoleLog').should('have.been.calledWith', 'downloading file archival') }) it('renders the AccessDatasetMenu if the user has download files permissions and the dataset is not deaccessioned', () => { diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx index 7d8974614..b1b197d1b 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/download-files/DownloadFilesButton.spec.tsx @@ -27,6 +27,12 @@ describe('DownloadFilesButton', () => { ) } + beforeEach(() => { + fileRepository.getMultipleFileDownloadUrl = cy + .stub() + .returns('https://multiple-file-download-url') + }) + it('renders the Download Files button if there is more than 1 file in the dataset and the user has download files permission', () => { const datasetWithDownloadFilesPermission = DatasetMother.create({ permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed() @@ -141,7 +147,6 @@ describe('DownloadFilesButton', () => { 'some-file-id': files[0], 'some-other-file-id': files[1] } - fileRepository.getMultipleFileDownloadUrl = cy.stub().returns('https://some-download-url') cy.mountAuthenticated( {withDataset( @@ -153,7 +158,7 @@ describe('DownloadFilesButton', () => { cy.findByRole('button', { name: 'Download' }) .parent('a') - .should('have.attr', 'href', 'https://some-download-url') + .should('have.attr', 'href', 'https://multiple-file-download-url') }) it('renders the download url for the selected files when some files are selected and there are tabular files', () => { @@ -172,7 +177,6 @@ describe('DownloadFilesButton', () => { 'some-file-id': files[0], 'some-other-file-id': files[1] } - fileRepository.getMultipleFileDownloadUrl = cy.stub().returns('https://some-download-url') cy.mountAuthenticated( {withDataset( @@ -186,12 +190,12 @@ describe('DownloadFilesButton', () => { cy.findByRole('link', { name: 'Original Format' }).should( 'have.attr', 'href', - 'https://some-download-url' + 'https://multiple-file-download-url' ) cy.findByRole('link', { name: 'Archival Format (.tab)' }).should( 'have.attr', 'href', - 'https://some-download-url' + 'https://multiple-file-download-url' ) }) @@ -200,8 +204,8 @@ describe('DownloadFilesButton', () => { permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), hasOneTabularFileAtLeast: true, downloadUrls: { - original: 'https://some-download-url', - archival: 'https://some-download-url' + original: 'https://dataset-download-url-original', + archival: 'https://dataset-download-url-archival' } }) const files = FileMother.createMany(2, { @@ -226,12 +230,50 @@ describe('DownloadFilesButton', () => { cy.findByRole('link', { name: 'Original Format' }).should( 'have.attr', 'href', - 'https://some-download-url' + 'https://dataset-download-url-original' + ) + cy.findByRole('link', { name: 'Archival Format (.tab)' }).should( + 'have.attr', + 'href', + 'https://dataset-download-url-archival' + ) + }) + + it('renders the dataset download url with the single file download url when one file is selected', () => { + const datasetWithDownloadFilesPermission = DatasetMother.create({ + permissions: DatasetPermissionsMother.createWithFilesDownloadAllowed(), + hasOneTabularFileAtLeast: true + }) + const files = FileMother.createMany(2, { + tabularData: { + variablesCount: 2, + observationsCount: 3, + unf: 'some-unf' + } + }) + const fileSelection = { + 'some-file-id': files[0] + } + fileRepository.getFileDownloadUrl = cy.stub().returns('https://single-file-download-url') + cy.mountAuthenticated( + + {withDataset( + , + datasetWithDownloadFilesPermission + )} + + ) + + cy.findByRole('button', { name: 'Download' }).click() + cy.findByRole('link', { name: 'Original Format' }).should( + 'have.attr', + 'href', + 'https://single-file-download-url' ) cy.findByRole('link', { name: 'Archival Format (.tab)' }).should( 'have.attr', 'href', - 'https://some-download-url' + 'https://single-file-download-url' ) }) }) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 327a655af..c7bc50488 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -372,7 +372,7 @@ describe('Dataset', () => { }) }) - it.only('applies filters to the Files Table in the correct order', () => { + it('applies filters to the Files Table in the correct order', () => { const files = [ FileHelper.create('csv', { description: 'Some description', From 422e17685a38acbf7d49a09e1f97d9b635821245 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Fri, 15 Dec 2023 09:48:57 -0500 Subject: [PATCH 58/58] add demo video of files table --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c6e758b18..a91b5db61 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ ## Demo videos - 2023-08-01: [View mode of the dataset page](https://groups.google.com/g/dataverse-community/c/cxZ3Bal_-uo/m/h3kh3iVNCwAJ) +- 2023-12-13: [Files table on the dataset page](https://groups.google.com/g/dataverse-community/c/w_rEMddESYc/m/6F7QC1p-AgAJ) ## Getting Started