From 35a7148ee0b9ae801b07b92ce1c7ae404ec504e5 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Thu, 26 Oct 2023 16:21:28 -0400 Subject: [PATCH 01/57] fix: try extending timeout for containers --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d962cc4f4..3f7ee55a0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,7 +49,7 @@ jobs: run: ./run-env.sh "$E2E_DATAVERSE_IMAGE_TAG" - name: Wait for containers to be ready - run: timeout 360s sh -c 'while ! docker logs dev_dataverse_bootstrap 2>&1 | grep -q "your instance has been configured"; do sleep 2; done' + run: timeout 720s sh -c 'while ! docker logs dev_dataverse_bootstrap 2>&1 | grep -q "your instance has been configured"; do sleep 2; done' - name: Run e2e tests run: npm run test:e2e From fe1c284c1cb4c91d95430881e307c209f3550813 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 27 Oct 2023 14:22:57 -0400 Subject: [PATCH 02/57] add step to test.yml worfklow to display logs --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3f7ee55a0..bfca4915b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,7 +49,11 @@ jobs: run: ./run-env.sh "$E2E_DATAVERSE_IMAGE_TAG" - name: Wait for containers to be ready - run: timeout 720s sh -c 'while ! docker logs dev_dataverse_bootstrap 2>&1 | grep -q "your instance has been configured"; do sleep 2; done' + run: timeout 360s sh -c 'while ! docker logs dev_dataverse_bootstrap 2>&1 | grep -q "your instance has been configured"; do sleep 2; done' + + - name: Display Docker logs + run: | + docker logs dev_dataverse_bootstrap - name: Run e2e tests run: npm run test:e2e From a5abd9ea376031150e6ff69453775578db35a62e Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 27 Oct 2023 14:47:35 -0400 Subject: [PATCH 03/57] show tail of log while wait for containers to be ready --- .github/workflows/test.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bfca4915b..e8d06489f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,8 +48,14 @@ jobs: working-directory: dev-env run: ./run-env.sh "$E2E_DATAVERSE_IMAGE_TAG" - - name: Wait for containers to be ready - run: timeout 360s sh -c 'while ! docker logs dev_dataverse_bootstrap 2>&1 | grep -q "your instance has been configured"; do sleep 2; done' + - - name: Wait for containers to be ready + run: | + timeout 360s sh -c 'while ! docker logs dev_dataverse_bootstrap 2>&1 | grep -q "your instance has been configured"; do + echo "Checking logs..." + docker logs dev_dataverse_bootstrap | tail -n 10 + echo "Text not found, waiting for 2 seconds..." + sleep 2 + done' - name: Display Docker logs run: | From 4a5caba17dcba357ce4fe2e427464f4799070ae4 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 27 Oct 2023 14:49:57 -0400 Subject: [PATCH 04/57] fix: syntax error in test.yml --- .github/workflows/test.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e8d06489f..a9d7fefb7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,14 +48,14 @@ jobs: working-directory: dev-env run: ./run-env.sh "$E2E_DATAVERSE_IMAGE_TAG" - - - name: Wait for containers to be ready - run: | - timeout 360s sh -c 'while ! docker logs dev_dataverse_bootstrap 2>&1 | grep -q "your instance has been configured"; do - echo "Checking logs..." - docker logs dev_dataverse_bootstrap | tail -n 10 - echo "Text not found, waiting for 2 seconds..." - sleep 2 - done' + - name: Wait for containers to be ready + run: | + timeout 360s sh -c 'while ! docker logs dev_dataverse_bootstrap 2>&1 | grep -q "your instance has been configured"; do + echo "Checking logs..." + docker logs dev_dataverse_bootstrap | tail -n 10 + echo "Text not found, waiting for 2 seconds..." + sleep 2 + done' - name: Display Docker logs run: | From b462fe5dfdb789e3e8fca1134706f57df50c0336 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 27 Oct 2023 15:51:38 -0400 Subject: [PATCH 05/57] use develop dataverse image --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a9d7fefb7..208bfee01 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,7 @@ name: test on: push env: - E2E_DATAVERSE_IMAGE_TAG: unstable + E2E_DATAVERSE_IMAGE_TAG: develop jobs: e2e: @@ -52,7 +52,7 @@ jobs: run: | timeout 360s sh -c 'while ! docker logs dev_dataverse_bootstrap 2>&1 | grep -q "your instance has been configured"; do echo "Checking logs..." - docker logs dev_dataverse_bootstrap | tail -n 10 + docker logs dev_dataverse_bootstrap | tail -n 5 echo "Text not found, waiting for 2 seconds..." sleep 2 done' From 7d2bce2aaa6a9c0cb6bfeabf03ca2227aba65373 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 27 Oct 2023 16:27:58 -0400 Subject: [PATCH 06/57] revert dataverse image to unstable --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 208bfee01..6b31f8e88 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,7 @@ name: test on: push env: - E2E_DATAVERSE_IMAGE_TAG: develop + E2E_DATAVERSE_IMAGE_TAG: unstable jobs: e2e: From 4e80cf893570b69b903c18df1c2a1073c69f40dc Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 31 Oct 2023 12:13:27 +0100 Subject: [PATCH 07/57] 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 8681f80ba92178aef99bda1ddcb2be6b71dc8e48 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Wed, 1 Nov 2023 15:45:00 -0400 Subject: [PATCH 08/57] feat: add TestUtils.waitForNoLocks() --- .../DatasetJSDataverseRepository.spec.ts | 4 +- tests/e2e-integration/shared/TestsUtils.ts | 30 ++++++++++ .../shared/datasets/DatasetHelper.ts | 60 ++++++++++++++----- 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index 65f01dbfc..dda9f60c6 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -113,9 +113,7 @@ describe('Dataset JSDataverse Repository', () => { it('gets the dataset by persistentId and version number', async () => { const datasetResponse = await DatasetHelper.create() await DatasetHelper.publish(datasetResponse.persistentId) - - await TestsUtils.wait(1500) - + await TestsUtils.waitForNoLocks(datasetResponse.persistentId) await datasetRepository .getByPersistentId(datasetResponse.persistentId, '1.0') .then((dataset) => { diff --git a/tests/e2e-integration/shared/TestsUtils.ts b/tests/e2e-integration/shared/TestsUtils.ts index 1ad0c0bdb..fd8f364ee 100644 --- a/tests/e2e-integration/shared/TestsUtils.ts +++ b/tests/e2e-integration/shared/TestsUtils.ts @@ -2,6 +2,7 @@ import { ApiConfig } from '@iqss/dataverse-client-javascript/dist/core' import { DataverseApiHelper } from './DataverseApiHelper' import { DataverseApiAuthMechanism } from '@iqss/dataverse-client-javascript/dist/core/infra/repositories/ApiConfig' import { UserJSDataverseRepository } from '../../../src/users/infrastructure/repositories/UserJSDataverseRepository' +import { DatasetHelper } from './datasets/DatasetHelper' export class TestsUtils { static readonly DATAVERSE_BACKEND_URL = @@ -25,4 +26,33 @@ export class TestsUtils { static logout() { return new UserJSDataverseRepository().removeAuthenticated() } + + static async waitForNoLocks(persistentId: string, maxRetries = 20, delay = 1000): Promise { + await this.checkForLocks(persistentId, maxRetries, delay) + } + + private static async checkForLocks( + persistentId: string, + maxRetries: number, + delay: number + ): Promise { + let retry = 0 + + while (retry < maxRetries) { + const response = await DatasetHelper.getLocks(persistentId) + console.log('Checking locks: ' + JSON.stringify(response)) + + // Check if response has a property "0" + if (Object.keys(response).length === 1) { + console.log('No locks found.') + return + } + + retry++ + await this.wait(delay) + } + + console.log('Max retries reached.') + throw new Error('Max retries reached.') + } } diff --git a/tests/e2e-integration/shared/datasets/DatasetHelper.ts b/tests/e2e-integration/shared/datasets/DatasetHelper.ts index 32143ec12..45b9eb03b 100644 --- a/tests/e2e-integration/shared/datasets/DatasetHelper.ts +++ b/tests/e2e-integration/shared/datasets/DatasetHelper.ts @@ -17,11 +17,24 @@ export class DatasetHelper extends DataverseApiHelper { return this.request(`/dataverses/root/datasets`, 'POST', newDatasetData) } - static async publish(persistentId: string): Promise<{ status: string; persistentId: string }> { - const response = await this.request<{ status: string }>( - `/datasets/:persistentId/actions/:publish?persistentId=${persistentId}&type=major`, - 'POST' - ) + static async publish(persistentId: string): Promise<{ + status: string + persistentId: string + }> { + const response = await this.request<{ + status: string + }>(`/datasets/:persistentId/actions/:publish?persistentId=${persistentId}&type=major`, 'POST') + + return { ...response, persistentId } + } + + static async getLocks(persistentId: string): Promise<{ + status: string + persistentId: string + }> { + const response = await this.request<{ + status: string + }>(`/datasets/:persistentId/locks?persistentId=${persistentId}`, 'GET') return { ...response, persistentId } } @@ -45,15 +58,20 @@ export class DatasetHelper extends DataverseApiHelper { .click() } - static async createPrivateUrl(id: string): Promise<{ token: string }> { - return this.request<{ token: string }>(`/datasets/${id}/privateUrl`, 'POST') + static async createPrivateUrl(id: string): Promise<{ + token: string + }> { + return this.request<{ + token: string + }>(`/datasets/${id}/privateUrl`, 'POST') } - static async createPrivateUrlAnonymized(id: string): Promise<{ token: string }> { - return this.request<{ token: string }>( - `/datasets/${id}/privateUrl?anonymizedAccess=true`, - 'POST' - ) + static async createPrivateUrlAnonymized(id: string): Promise<{ + token: string + }> { + return this.request<{ + token: string + }>(`/datasets/${id}/privateUrl?anonymizedAccess=true`, 'POST') } static async createWithFiles(filesData: FileData[]): Promise { @@ -92,7 +110,15 @@ export class DatasetHelper extends DataverseApiHelper { datasetPersistentId: string, fileData: FileData ): Promise { - const { files } = await this.request<{ files: [{ dataFile: { id: number } }] }>( + const { files } = await this.request<{ + files: [ + { + dataFile: { + id: number + } + } + ] + }>( `/datasets/:persistentId/add?persistentId=${datasetPersistentId}`, 'POST', fileData, @@ -108,8 +134,12 @@ export class DatasetHelper extends DataverseApiHelper { static async setCitationDateFieldType( persistentId: string, fieldType: string - ): Promise<{ status: string }> { - return this.request<{ status: string }>( + ): Promise<{ + status: string + }> { + return this.request<{ + status: string + }>( `/datasets/:persistentId/citationdate?persistentId=${persistentId}`, 'PUT', fieldType, From f32407413ad89e50b937680f90df54007ad988ff Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Wed, 1 Nov 2023 17:02:23 -0400 Subject: [PATCH 09/57] add more calls to waitForLocks() --- .../DatasetJSDataverseRepository.spec.ts | 2 +- .../files/FileJSDataverseRepository.spec.ts | 22 +++++++++---------- tests/e2e-integration/shared/TestsUtils.ts | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index dda9f60c6..796d88044 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -175,7 +175,7 @@ describe('Dataset JSDataverse Repository', () => { const datasetResponse = await DatasetHelper.create() await DatasetHelper.publish(datasetResponse.persistentId) - await TestsUtils.wait(1500) + await TestsUtils.waitForNoLocks(datasetResponse.persistentId) await DatasetHelper.setCitationDateFieldType(datasetResponse.persistentId, 'dateOfDeposit') diff --git a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts index 1dd07b335..8e5938028 100644 --- a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts @@ -129,7 +129,7 @@ describe('File JSDataverse Repository', () => { if (!dataset) throw new Error('Dataset not found') await DatasetHelper.publish(dataset.persistentId) - await TestsUtils.wait(1500) // Wait for the dataset to be published + await TestsUtils.waitForNoLocks(dataset.persistentId) // Wait for the dataset to be published await fileRepository .getAllByDatasetPersistentId( @@ -163,7 +163,7 @@ describe('File JSDataverse Repository', () => { if (!dataset) throw new Error('Dataset not found') await DatasetHelper.publish(dataset.persistentId) - await TestsUtils.wait(1500) // Wait for the dataset to be published + await TestsUtils.waitForNoLocks(dataset.persistentId) // Wait for the dataset to be published DatasetHelper.deaccession(dataset.persistentId) await TestsUtils.wait(1500) // Wait for the dataset to be deaccessioned @@ -199,7 +199,7 @@ describe('File JSDataverse Repository', () => { if (!datasetResponse.files) throw new Error('Files not found') await DatasetHelper.publish(datasetResponse.persistentId) - await TestsUtils.wait(1500) // Wait for the dataset to be published + await TestsUtils.waitForNoLocks(datasetResponse.persistentId) // Wait for the dataset to be published const dataset = await datasetRepository.getByPersistentId(datasetResponse.persistentId) if (!dataset) throw new Error('Dataset not found') @@ -238,8 +238,7 @@ describe('File JSDataverse Repository', () => { it('gets all the files by dataset persistentId after adding tag labels to the files', async () => { const datasetResponse = await DatasetHelper.createWithFiles(FileHelper.createMany(1, 'csv')) if (!datasetResponse.files) throw new Error('Files not found') - await TestsUtils.wait(1500) // Wait for the tabular data to be ingested - + await TestsUtils.waitForNoLocks(datasetResponse.persistentId) // Wait for the tabular data to be ingested const dataset = await datasetRepository.getByPersistentId(datasetResponse.persistentId) if (!dataset) throw new Error('Dataset not found') @@ -270,7 +269,7 @@ describe('File JSDataverse Repository', () => { [datasetResponse.files[0].id, datasetResponse.files[1].id, datasetResponse.files[2].id], embargoDate ) - await TestsUtils.wait(1500) // Wait for the files to be embargoed + await TestsUtils.waitForNoLocks(datasetResponse.persistentId) // Wait for the files to be embargoed await fileRepository .getAllByDatasetPersistentId(dataset.persistentId, dataset.version) @@ -490,11 +489,11 @@ describe('File JSDataverse Repository', () => { total: 6, perAccess: [ { - access: FileAccessOption.RESTRICTED, + access: FileAccessOption.PUBLIC, count: 3 }, { - access: FileAccessOption.PUBLIC, + access: FileAccessOption.RESTRICTED, count: 3 } ], @@ -519,12 +518,13 @@ describe('File JSDataverse Repository', () => { } ] } - + let filesCountInfo: FilesCountInfo | undefined = undefined await fileRepository .getFilesCountInfoByDatasetPersistentId(dataset.persistentId, dataset.version) - .then((filesCountInfo) => { - expect(filesCountInfo).to.deep.equal(expectedFilesCountInfo) + .then((filesCountInfoResp) => { + filesCountInfo = filesCountInfoResp }) + expect(filesCountInfo).to.deep.equal(expectedFilesCountInfo) }) }) }) diff --git a/tests/e2e-integration/shared/TestsUtils.ts b/tests/e2e-integration/shared/TestsUtils.ts index fd8f364ee..f6508dcfd 100644 --- a/tests/e2e-integration/shared/TestsUtils.ts +++ b/tests/e2e-integration/shared/TestsUtils.ts @@ -42,7 +42,7 @@ export class TestsUtils { const response = await DatasetHelper.getLocks(persistentId) console.log('Checking locks: ' + JSON.stringify(response)) - // Check if response has a property "0" + // The response will have a single key if there are no locks if (Object.keys(response).length === 1) { console.log('No locks found.') return From d3eb838851438078239f083ac537c61235944bb0 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Thu, 2 Nov 2023 10:06:59 +0100 Subject: [PATCH 10/57] 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 11/57] 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 4bf1c82830be60942ef23c4cf03226ad4244f8e4 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Thu, 2 Nov 2023 21:40:44 -0400 Subject: [PATCH 12/57] fix: merge commit --- src/files/infrastructure/mappers/DomainFileMapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/files/infrastructure/mappers/DomainFileMapper.ts b/src/files/infrastructure/mappers/DomainFileMapper.ts index ae0a673e6..71082838d 100644 --- a/src/files/infrastructure/mappers/DomainFileMapper.ts +++ b/src/files/infrastructure/mappers/DomainFileMapper.ts @@ -7,7 +7,7 @@ import { } from '../../domain/models/FileCriteria' import { FileAccessStatus as JSFileAccessStatus, - FileCriteria as JSFileSearchCriteria, + FileSearchCriteria as JSFileSearchCriteria, FileOrderCriteria as JSFileOrderCriteria } from '@iqss/dataverse-client-javascript' import { FileType } from '../../domain/models/File' From cdc9de2c2ae114665716f813915b9ba82865ab6e Mon Sep 17 00:00:00 2001 From: MellyGray Date: Fri, 3 Nov 2023 11:26:57 +0100 Subject: [PATCH 13/57] 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 14/57] 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 15/57] 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 16/57] 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 17/57] 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 18/57] 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 d2bc0f8e823067118a4011bfcf67285fe61e4e6e Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 3 Nov 2023 13:47:38 -0400 Subject: [PATCH 19/57] fix: timezone and timing issue in e2e test --- package-lock.json | 20 +++++++++++++++++++ package.json | 1 + .../e2e/sections/dataset/Dataset.spec.tsx | 17 +++++++++++++--- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9cab908e7..ae9b44949 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "i18next": "22.4.9", "i18next-browser-languagedetector": "7.0.1", "i18next-http-backend": "2.1.1", + "moment-timezone": "^0.5.43", "react-bootstrap": "2.7.2", "react-bootstrap-icons": "1.10.3", "react-i18next": "12.1.5", @@ -31965,6 +31966,25 @@ "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==", "dev": true }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.43", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz", + "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", diff --git a/package.json b/package.json index 0c61160a5..6c826d6b0 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "i18next": "22.4.9", "i18next-browser-languagedetector": "7.0.1", "i18next-http-backend": "2.1.1", + "moment-timezone": "^0.5.43", "react-bootstrap": "2.7.2", "react-bootstrap-icons": "1.10.3", "react-i18next": "12.1.5", diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index ef743d439..a0ec9c0f0 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -2,6 +2,7 @@ import { DatasetLabelValue } from '../../../../../src/dataset/domain/models/Data import { TestsUtils } from '../../../shared/TestsUtils' import { DatasetHelper } from '../../../shared/datasets/DatasetHelper' import { FileHelper } from '../../../shared/files/FileHelper' +import moment from 'moment-timezone' type Dataset = { datasetVersion: { metadataBlocks: { citation: { fields: { value: string }[] } } } @@ -265,18 +266,28 @@ describe('Dataset', () => { cy.findByText('Restricted with access Icon').should('not.exist') cy.findByText('Restricted File Icon').should('exist') - cy.findByRole('button', { name: 'Access File' }).should('exist').click() + // use alias below to avoid a timing error + cy.findByRole('button', { name: 'Access File' }).as('accessButton') + cy.get('@accessButton').should('exist') + cy.get('@accessButton').click() cy.findByText('Restricted').should('exist') }) }) it('loads the embargoed files', () => { + // Create a moment object in UTC and set the time to 12 AM (midnight) + const utcDate = moment.utc().startOf('day') + + // Add 100 years to the UTC date + utcDate.add(100, 'years') + const dateString = utcDate.format('YYYY-MM-DD') + const expectedDate = utcDate.local().format('MMM D, YYYY') cy.wrap( DatasetHelper.createWithFiles(FileHelper.createMany(1)).then((dataset) => DatasetHelper.embargoFiles( dataset.persistentId, [dataset.files ? dataset.files[0].id : 0], - '2100-10-20' + dateString ) ) ) @@ -291,7 +302,7 @@ describe('Dataset', () => { cy.findByText('Files').should('exist') cy.findByText(/Deposited/).should('exist') - cy.findByText('Draft: will be embargoed until Oct 20, 2100').should('exist') + cy.findByText(`Draft: will be embargoed until ${expectedDate}`).should('exist') cy.findByText('Edit Files').should('exist') From 41e987cb8096d18768070206a5ba32ad2598bfd4 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Sat, 4 Nov 2023 11:14:04 -0400 Subject: [PATCH 20/57] feat: Add DatasetAlertContext for edit alerts --- public/locales/en/dataset.json | 4 +++ src/dataset/domain/models/Dataset.ts | 3 +- src/sections/dataset/DatasetAlertContext.ts | 14 ++++++++++ src/sections/dataset/DatasetAlertProvider.tsx | 20 +++++++++++++ src/sections/dataset/DatasetFactory.tsx | 5 +++- .../dataset/dataset-alerts/DatasetAlerts.tsx | 4 ++- .../dataset-alerts/DatasetAlert.stories.tsx | 28 ++++++++++++++++++- 7 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/sections/dataset/DatasetAlertContext.ts create mode 100644 src/sections/dataset/DatasetAlertProvider.tsx diff --git a/public/locales/en/dataset.json b/public/locales/en/dataset.json index 916ab30b4..cd748bdd5 100644 --- a/public/locales/en/dataset.json +++ b/public/locales/en/dataset.json @@ -58,6 +58,10 @@ "uploadFiles": "Upload Files" }, "alerts": { + "updateMetadataSuccess": { + "heading": "Success!", + "alertText": "The metadata for this dataset has been updated." + }, "draftVersion": { "heading": "This draft version needs to be published", "alertText": "When ready for sharing, please publish it so that others can see these changes" diff --git a/src/dataset/domain/models/Dataset.ts b/src/dataset/domain/models/Dataset.ts index 409a7ab5e..203b33b77 100644 --- a/src/dataset/domain/models/Dataset.ts +++ b/src/dataset/domain/models/Dataset.ts @@ -29,7 +29,8 @@ export enum DatasetAlertMessageKey { REQUESTED_VERSION_NOT_FOUND = 'requestedVersionNotFound', REQUESTED_VERSION_NOT_FOUND_SHOW_DRAFT = 'requestedVersionNotFoundShowDraft', SHARE_UNPUBLISHED_DATASET = 'shareUnpublishedDataset', - UNPUBLISHED_DATASET = 'unpublishedDataset' + UNPUBLISHED_DATASET = 'unpublishedDataset', + UPDATE_METADATA_SUCCESS = 'updateMetadataSuccess' } export class DatasetAlert { diff --git a/src/sections/dataset/DatasetAlertContext.ts b/src/sections/dataset/DatasetAlertContext.ts new file mode 100644 index 000000000..0ebf4b02c --- /dev/null +++ b/src/sections/dataset/DatasetAlertContext.ts @@ -0,0 +1,14 @@ +import { createContext, useContext } from 'react' + +import { DatasetAlert } from '../../dataset/domain/models/Dataset' + +interface DatasetAlertContextProps { + datasetAlerts: DatasetAlert[] + setDatasetAlerts: (datasetAlerts: DatasetAlert[]) => void +} + +export const DatasetAlertContext = createContext({ + datasetAlerts: [], + setDatasetAlerts: /* istanbul ignore next */ () => {} +}) +export const useDatasetAlertContext = () => useContext(DatasetAlertContext) diff --git a/src/sections/dataset/DatasetAlertProvider.tsx b/src/sections/dataset/DatasetAlertProvider.tsx new file mode 100644 index 000000000..3c58d4a5e --- /dev/null +++ b/src/sections/dataset/DatasetAlertProvider.tsx @@ -0,0 +1,20 @@ +import { PropsWithChildren, useState } from 'react' +import { DatasetAlertContext } from './DatasetAlertContext' + +import { DatasetAlert } from '../../dataset/domain/models/Dataset' + +export const DatasetAlertProvider = ({ children }: PropsWithChildren) => { + const [datasetAlerts, setDatasetAlerts] = useState([]) + + // This function will be accessible by any child component to update the datasetAlerts state + const handleSetDatasetAlerts = (alerts: DatasetAlert[]) => { + setDatasetAlerts(alerts) + } + + return ( + + {children} + + ) +} diff --git a/src/sections/dataset/DatasetFactory.tsx b/src/sections/dataset/DatasetFactory.tsx index 1ac3e1e15..447bcb4cb 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 { DatasetAlertProvider } from './DatasetAlertProvider' const datasetRepository = new DatasetJSDataverseRepository() const fileRepository = new FileJSDataverseRepository() @@ -24,7 +25,9 @@ export class DatasetFactory { - + + + diff --git a/src/sections/dataset/dataset-alerts/DatasetAlerts.tsx b/src/sections/dataset/dataset-alerts/DatasetAlerts.tsx index 7d41990c0..35908f08e 100644 --- a/src/sections/dataset/dataset-alerts/DatasetAlerts.tsx +++ b/src/sections/dataset/dataset-alerts/DatasetAlerts.tsx @@ -3,6 +3,7 @@ import { DatasetAlert } from '../../../dataset/domain/models/Dataset' import { useTranslation } from 'react-i18next' import styles from './DatasetAlerts.module.scss' import parse from 'html-react-parser' +import { useDatasetAlertContext } from '../DatasetAlertContext' interface DatasetAlertsProps { alerts: DatasetAlert[] @@ -10,7 +11,8 @@ interface DatasetAlertsProps { export function DatasetAlerts({ alerts }: DatasetAlertsProps) { const { t } = useTranslation('dataset') - + const statusAlerts = useDatasetAlertContext() + alerts = alerts.concat(statusAlerts.datasetAlerts) return (
{alerts.map((alert: DatasetAlert, index) => { diff --git a/src/stories/dataset/dataset-alerts/DatasetAlert.stories.tsx b/src/stories/dataset/dataset-alerts/DatasetAlert.stories.tsx index 072994ad5..356e50140 100644 --- a/src/stories/dataset/dataset-alerts/DatasetAlert.stories.tsx +++ b/src/stories/dataset/dataset-alerts/DatasetAlert.stories.tsx @@ -1,6 +1,11 @@ import type { Meta, StoryObj } from '@storybook/react' -import { DatasetPublishingStatus, DatasetVersion } from '../../../dataset/domain/models/Dataset' +import { + DatasetAlert, + DatasetAlertMessageKey, + DatasetPublishingStatus, + DatasetVersion +} from '../../../dataset/domain/models/Dataset' import { DatasetAlerts } from '../../../sections/dataset/dataset-alerts/DatasetAlerts' import { WithI18next } from '../../WithI18next' @@ -8,15 +13,36 @@ import { DatasetMother, DatasetPermissionsMother } from '../../../../tests/component/dataset/domain/models/DatasetMother' +import { DatasetAlertContext } from '../../../sections/dataset/DatasetAlertContext' const meta: Meta = { title: 'Sections/Dataset Page/DatasetAlerts', component: DatasetAlerts, decorators: [WithI18next] } +const editMetadataAlert = [ + new DatasetAlert('success', DatasetAlertMessageKey.UPDATE_METADATA_SUCCESS) +] export default meta type Story = StoryObj +export const EditMetadataSuccessful: Story = { + render: () => { + const dataset = DatasetMother.createRealistic() + + return ( + {} + }}> +
+ +
+
+ ) + } +} export const DraftVersion: Story = { render: () => { From de2fcd8a6e507cb7cd255be4ccb11c243d6ad476 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Sun, 5 Nov 2023 21:19:29 -0500 Subject: [PATCH 21/57] feat: Add Stories and DatasetAlert types --- public/locales/en/dataset.json | 22 +++++++++++- src/dataset/domain/models/Dataset.ts | 9 +++-- .../dataset-alerts/DatasetAlert.stories.tsx | 36 +++++++++++++++++-- .../dataset-alerts/DatasetAlerts.spec.tsx | 6 ++++ 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/public/locales/en/dataset.json b/public/locales/en/dataset.json index cd748bdd5..fbc62fefb 100644 --- a/public/locales/en/dataset.json +++ b/public/locales/en/dataset.json @@ -58,7 +58,27 @@ "uploadFiles": "Upload Files" }, "alerts": { - "updateMetadataSuccess": { + "publishInProgress": { + "heading": "Publish in Progress", + "alertText": "The dataset is locked while the persistent identifiers are being registered or updated, and/or the physical files are being validated." + }, + "filesUpdated": { + "heading": "Success!", + "alertText": "One or more files have been updated." + }, + "termsUpdated": { + "heading": "Success!", + "alertText": "The terms for this dataset have been updated." + }, + "thumbnailUpdated": { + "heading": "Success!", + "alertText": "Dataset thumbnail updated." + }, + "datasetDeleted": { + "heading": "Success!", + "alertText": "This dataset draft has been deleted." + }, + "metadataUpdated": { "heading": "Success!", "alertText": "The metadata for this dataset has been updated." }, diff --git a/src/dataset/domain/models/Dataset.ts b/src/dataset/domain/models/Dataset.ts index 203b33b77..a12578325 100644 --- a/src/dataset/domain/models/Dataset.ts +++ b/src/dataset/domain/models/Dataset.ts @@ -30,14 +30,19 @@ export enum DatasetAlertMessageKey { REQUESTED_VERSION_NOT_FOUND_SHOW_DRAFT = 'requestedVersionNotFoundShowDraft', SHARE_UNPUBLISHED_DATASET = 'shareUnpublishedDataset', UNPUBLISHED_DATASET = 'unpublishedDataset', - UPDATE_METADATA_SUCCESS = 'updateMetadataSuccess' + METADATA_UPDATED = 'metadataUpdated', + FILES_UPDATED = 'filesUpdated', + TERMS_UPDATED = 'termsUpdated', + THUMBNAIL_UPDATED = 'thumbnailUpdated', + DATASET_DELETED = 'datasetDeleted', + PUBLISH_IN_PROGRESS = 'publishInProgress' } export class DatasetAlert { constructor( public readonly variant: AlertVariant, public readonly message: DatasetAlertMessageKey, - public readonly dynamicFields?: object + public dynamicFields?: object ) {} } diff --git a/src/stories/dataset/dataset-alerts/DatasetAlert.stories.tsx b/src/stories/dataset/dataset-alerts/DatasetAlert.stories.tsx index 356e50140..3493f2686 100644 --- a/src/stories/dataset/dataset-alerts/DatasetAlert.stories.tsx +++ b/src/stories/dataset/dataset-alerts/DatasetAlert.stories.tsx @@ -14,15 +14,14 @@ import { DatasetPermissionsMother } from '../../../../tests/component/dataset/domain/models/DatasetMother' import { DatasetAlertContext } from '../../../sections/dataset/DatasetAlertContext' +import { useEffect, useState } from 'react' const meta: Meta = { title: 'Sections/Dataset Page/DatasetAlerts', component: DatasetAlerts, decorators: [WithI18next] } -const editMetadataAlert = [ - new DatasetAlert('success', DatasetAlertMessageKey.UPDATE_METADATA_SUCCESS) -] +const editMetadataAlert = [new DatasetAlert('success', DatasetAlertMessageKey.METADATA_UPDATED)] export default meta type Story = StoryObj @@ -44,6 +43,37 @@ export const EditMetadataSuccessful: Story = { } } +const publishAlert = [new DatasetAlert('warning', DatasetAlertMessageKey.PUBLISH_IN_PROGRESS)] + +export const PublishInProgress: StoryObj = { + render: () => { + const dataset = DatasetMother.createRealistic() + const [alerts, setAlerts] = useState(publishAlert) + + // Set a timeout to remove the alert after 3 seconds + useEffect(() => { + const timer = setTimeout(() => { + setAlerts([]) // This will clear the alert after 3 seconds + }, 3000) // Timeout of 3 seconds + + // Clear the timer if the component unmounts + return () => clearTimeout(timer) + }, []) + + return ( + +
+ +
+
+ ) + } +} + export const DraftVersion: Story = { render: () => { const dataset = DatasetMother.createRealistic({ diff --git a/tests/component/sections/dataset/dataset-alerts/DatasetAlerts.spec.tsx b/tests/component/sections/dataset/dataset-alerts/DatasetAlerts.spec.tsx index d70fd4977..03430b233 100644 --- a/tests/component/sections/dataset/dataset-alerts/DatasetAlerts.spec.tsx +++ b/tests/component/sections/dataset/dataset-alerts/DatasetAlerts.spec.tsx @@ -28,6 +28,12 @@ interface DatasetTranslation { [DatasetAlertMessageKey.REQUESTED_VERSION_NOT_FOUND_SHOW_DRAFT]: AlertTranslation [DatasetAlertMessageKey.UNPUBLISHED_DATASET]: AlertTranslation [DatasetAlertMessageKey.SHARE_UNPUBLISHED_DATASET]: AlertTranslation + [DatasetAlertMessageKey.METADATA_UPDATED]: AlertTranslation + [DatasetAlertMessageKey.FILES_UPDATED]: AlertTranslation + [DatasetAlertMessageKey.PUBLISH_IN_PROGRESS]: AlertTranslation + [DatasetAlertMessageKey.TERMS_UPDATED]: AlertTranslation + [DatasetAlertMessageKey.DATASET_DELETED]: AlertTranslation + [DatasetAlertMessageKey.THUMBNAIL_UPDATED]: AlertTranslation } } From 4c71c0475d77a33f5447e73fb819317b80017405 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 6 Nov 2023 10:27:07 +0100 Subject: [PATCH 22/57] 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 971a32e035be749cb6de83fb5c813c3d209d2df3 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 6 Nov 2023 15:59:36 +0100 Subject: [PATCH 23/57] feat(IntegrationDatasetLocks): add integration test --- package-lock.json | 8 +++--- package.json | 2 +- .../DatasetJSDataverseRepository.spec.ts | 25 ++++++++++++++++++- .../shared/datasets/DatasetHelper.ts | 12 +++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index ae9b44949..07dff0099 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-pr97.418bf5e", + "@iqss/dataverse-client-javascript": "2.0.0-pr99.c36f1db", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -3589,9 +3589,9 @@ }, "node_modules/@iqss/dataverse-client-javascript": { "name": "@IQSS/dataverse-client-javascript", - "version": "2.0.0-pr97.418bf5e", - "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr97.418bf5e/0d604232d6f567b41328143c2d5d27513d069380", - "integrity": "sha512-wKRR1ORFkFSoFoCrWsCQvcm1vExIIrVN3pJbIl9Nk8lopP7zkUEB1xcAU+UVShbGktIZIa20TQMFl0ONafDiZw==", + "version": "2.0.0-pr99.c36f1db", + "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr99.c36f1db/3f49037b14e53295c39ce787cce53f20b2558ba6", + "integrity": "sha512-KzMVzB420eKKaOuwDEpvAB/k1RrW3Le/ZJcVtjxFk/Wvxov2Jl1npbwy4SXQWasEXaJWslohn2KRkBfBDoTHTQ==", "license": "MIT", "dependencies": { "@types/node": "^18.15.11", diff --git a/package.json b/package.json index 6c826d6b0..7de72ef50 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-pr97.418bf5e", + "@iqss/dataverse-client-javascript": "2.0.0-pr99.c36f1db", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index 796d88044..99a2b163b 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -3,6 +3,7 @@ import chaiAsPromised from 'chai-as-promised' import { DatasetJSDataverseRepository } from '../../../../src/dataset/infrastructure/repositories/DatasetJSDataverseRepository' import { TestsUtils } from '../../shared/TestsUtils' import { + DatasetLockReason, DatasetPublishingStatus, DatasetVersion } from '../../../../src/dataset/domain/models/Dataset' @@ -80,7 +81,8 @@ const datasetData = (persistentId: string, versionId: number) => { latestVersionStatus: 'draft', isLatest: true, isInReview: false - } + }, + locks: [] } } @@ -107,6 +109,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.locks).to.deep.equal(datasetExpected.locks) }) }) @@ -192,4 +195,24 @@ describe('Dataset JSDataverse Repository', () => { expect(dataset.metadataBlocks[0].fields.citationDate).not.to.exist }) }) + + it('gets the dataset when is locked', async () => { + const datasetResponse = await DatasetHelper.create() + await DatasetHelper.lock(datasetResponse.id, DatasetLockReason.FINALIZE_PUBLICATION) + + await datasetRepository.getByPersistentId(datasetResponse.persistentId).then((dataset) => { + if (!dataset) { + throw new Error('Dataset not found') + } + const datasetExpected = datasetData(dataset.persistentId, dataset.version.id) + + expect(dataset.getTitle()).to.deep.equal(datasetExpected.title) + expect(dataset.locks).to.deep.equal([ + { + id: 0, + reason: DatasetLockReason.FINALIZE_PUBLICATION + } + ]) + }) + }) }) diff --git a/tests/e2e-integration/shared/datasets/DatasetHelper.ts b/tests/e2e-integration/shared/datasets/DatasetHelper.ts index 45b9eb03b..962dfa4d5 100644 --- a/tests/e2e-integration/shared/datasets/DatasetHelper.ts +++ b/tests/e2e-integration/shared/datasets/DatasetHelper.ts @@ -1,6 +1,7 @@ import newDatasetData from '../../fixtures/dataset-finch1.json' import { DataverseApiHelper } from '../DataverseApiHelper' import { FileData } from '../files/FileHelper' +import { DatasetLockReason } from '../../../../src/dataset/domain/models/Dataset' export interface DatasetResponse { persistentId: string @@ -146,4 +147,15 @@ export class DatasetHelper extends DataverseApiHelper { 'text/plain' ) } + + static async lock( + id: string, + reason: DatasetLockReason + ): Promise<{ + status: string + }> { + return this.request<{ + status: string + }>(`/datasets/${id}/lock/${reason}`, 'POST') + } } From 44dccbd5c8c0c17fe054d5f78793eaafbcdc3240 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 6 Nov 2023 16:07:06 +0100 Subject: [PATCH 24/57] feat(IntegrationDatasetLocks): add integration with getDatasetLocks use case --- src/dataset/domain/models/Dataset.ts | 15 +++++------ .../infrastructure/mappers/JSDatasetMapper.ts | 19 ++++++++++--- .../DatasetJSDataverseRepository.ts | 27 ++++++++++++++----- .../DatasetJSDataverseRepository.spec.ts | 2 +- 4 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/dataset/domain/models/Dataset.ts b/src/dataset/domain/models/Dataset.ts index 409a7ab5e..497947c21 100644 --- a/src/dataset/domain/models/Dataset.ts +++ b/src/dataset/domain/models/Dataset.ts @@ -264,15 +264,14 @@ export interface DatasetLock { } export enum DatasetLockReason { - INGEST = 'ingest', - WORKFLOW = 'workflow', - IN_REVIEW = 'inReview', - DCM_UPLOAD = 'dcmUpload', - GLOBUS_UPLOAD = 'globusUpload', + INGEST = 'Ingest', + WORKFLOW = 'Workflow', + IN_REVIEW = 'InReview', + DCM_UPLOAD = 'DcmUpload', + GLOBUS_UPLOAD = 'GlobusUpload', FINALIZE_PUBLICATION = 'finalizePublication', - - EDIT_IN_PROGRESS = 'editInProgress', - FILE_VALIDATION_FAILED = 'fileValidationFailed' + EDIT_IN_PROGRESS = 'EditInProgress', + FILE_VALIDATION_FAILED = 'FileValidationFailed' } export interface PrivateUrl { diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index 77a8c3031..049055037 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, + DatasetLock as JSDatasetLock } from '@iqss/dataverse-client-javascript' import { DatasetVersionState as JSDatasetVersionState } from '@iqss/dataverse-client-javascript/dist/datasets/domain/models/Dataset' import { @@ -14,7 +15,9 @@ import { DatasetMetadataFields, DatasetVersion, MetadataBlockName, - PrivateUrl + PrivateUrl, + DatasetLock, + DatasetLockReason } from '../../domain/models/Dataset' export class JSDatasetMapper { @@ -22,6 +25,7 @@ export class JSDatasetMapper { jsDataset: JSDataset, citation: string, summaryFieldsNames: string[], + jsDatasetLocks: JSDatasetLock[], requestedVersion?: string, privateUrl?: PrivateUrl ): Dataset { @@ -45,7 +49,7 @@ export class JSDatasetMapper { canManageFilesPermissions: true, canDeleteDataset: true }, // TODO Connect with dataset permissions - [], // TODO Connect with dataset locks + JSDatasetMapper.toLocks(jsDatasetLocks), true, // TODO Connect with dataset hasValidTermsOfAccess true, // TODO Connect with dataset isValid jsDataset.versionInfo.releaseTime !== undefined && @@ -185,4 +189,13 @@ export class JSDatasetMapper { return extraFields } + + static toLocks(jsDatasetLocks: JSDatasetLock[]): DatasetLock[] { + return jsDatasetLocks.map((jsDatasetLock) => { + return { + id: 0, + reason: jsDatasetLock.lockType as unknown as DatasetLockReason + } + }) + } } diff --git a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts index c3fc7fbf0..239724296 100644 --- a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts +++ b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts @@ -7,7 +7,9 @@ import { Dataset as JSDataset, getPrivateUrlDataset, getPrivateUrlDatasetCitation, - ReadError + ReadError, + getDatasetLocks, + DatasetLock as JSDatasetLock } from '@iqss/dataverse-client-javascript' import { JSDatasetMapper } from '../mappers/JSDatasetMapper' @@ -23,11 +25,24 @@ export class DatasetJSDataverseRepository implements DatasetRepository { Promise.all([ jsDataset, getDatasetSummaryFieldNames.execute(), - getDatasetCitation.execute(jsDataset.id, this.versionToVersionId(version)) + getDatasetCitation.execute(jsDataset.id, this.versionToVersionId(version)), + getDatasetLocks.execute(jsDataset.id) ]) ) - .then(([jsDataset, summaryFieldsNames, citation]: [JSDataset, string[], string]) => - JSDatasetMapper.toDataset(jsDataset, citation, summaryFieldsNames, requestedVersion) + .then( + ([jsDataset, summaryFieldsNames, citation, jsDatasetLocks]: [ + JSDataset, + string[], + string, + JSDatasetLock[] + ]) => + JSDatasetMapper.toDataset( + jsDataset, + citation, + summaryFieldsNames, + jsDatasetLocks, + requestedVersion + ) ) .catch((error: ReadError) => { if (!version) { @@ -42,9 +57,9 @@ export class DatasetJSDataverseRepository implements DatasetRepository { getPrivateUrlDataset.execute(privateUrlToken), getDatasetSummaryFieldNames.execute(), getPrivateUrlDatasetCitation.execute(privateUrlToken) - ]) + ]) // TODO - Add getDatasetLocks.execute(privateUrlToken) when it is available in js-dataverse .then(([jsDataset, summaryFieldsNames, citation]: [JSDataset, string[], string]) => - JSDatasetMapper.toDataset(jsDataset, citation, summaryFieldsNames, undefined) + JSDatasetMapper.toDataset(jsDataset, citation, summaryFieldsNames, []) ) .catch((error: ReadError) => { throw new Error(error.message) diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index 99a2b163b..ae8ee219f 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -196,7 +196,7 @@ describe('Dataset JSDataverse Repository', () => { }) }) - it('gets the dataset when is locked', async () => { + it('gets the dataset by persistentId when is locked', async () => { const datasetResponse = await DatasetHelper.create() await DatasetHelper.lock(datasetResponse.id, DatasetLockReason.FINALIZE_PUBLICATION) From 5e9b75e9155c31e6a93ca7ca8cbf0477a2a8c699 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 6 Nov 2023 16:19:13 +0100 Subject: [PATCH 25/57] feat(IntegrationDatasetLocks): add persistentId to User model --- src/users/domain/models/User.ts | 1 + .../infrastructure/repositories/UserJSDataverseRepository.ts | 5 ++++- tests/component/users/domain/models/UserMother.ts | 3 ++- .../repositories/UserJSDataverseRepository.spec.ts | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/users/domain/models/User.ts b/src/users/domain/models/User.ts index be3cb9315..1b7885f5d 100644 --- a/src/users/domain/models/User.ts +++ b/src/users/domain/models/User.ts @@ -1,3 +1,4 @@ export interface User { name: string + persistentId: string } diff --git a/src/users/infrastructure/repositories/UserJSDataverseRepository.ts b/src/users/infrastructure/repositories/UserJSDataverseRepository.ts index 69ea18d64..dd8c6607d 100644 --- a/src/users/infrastructure/repositories/UserJSDataverseRepository.ts +++ b/src/users/infrastructure/repositories/UserJSDataverseRepository.ts @@ -11,7 +11,10 @@ export class UserJSDataverseRepository implements UserRepository { return getCurrentAuthenticatedUser .execute() .then((authenticatedUser: AuthenticatedUser) => { - return { name: authenticatedUser.displayName } + return { + name: authenticatedUser.displayName, + persistentId: authenticatedUser.persistentUserId + } }) .catch((error: ReadError) => { throw new Error(error.message) diff --git a/tests/component/users/domain/models/UserMother.ts b/tests/component/users/domain/models/UserMother.ts index 9c35f94e8..3d2052242 100644 --- a/tests/component/users/domain/models/UserMother.ts +++ b/tests/component/users/domain/models/UserMother.ts @@ -3,7 +3,8 @@ import { User } from '../../../../../src/users/domain/models/User' export class UserMother { static create(): User { return { - name: 'James D. Potts' + name: 'James D. Potts', + persistentId: 'jamesPotts' } } } diff --git a/tests/e2e-integration/integration/users/infrastructure/repositories/UserJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/users/infrastructure/repositories/UserJSDataverseRepository.spec.ts index ac4100e3c..efd036c88 100644 --- a/tests/e2e-integration/integration/users/infrastructure/repositories/UserJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/users/infrastructure/repositories/UserJSDataverseRepository.spec.ts @@ -12,7 +12,7 @@ describe('User JSDataverse Repository', () => { beforeEach(() => TestsUtils.login()) it('gets the authenticated user', async () => { - const expectedUser = { name: 'Dataverse Admin' } + const expectedUser = { name: 'Dataverse Admin', persistentId: 'dataverseAdmin' } const user = await userRepository.getAuthenticated() expect(user).to.deep.equal(expectedUser) From 892109aef06369f4abd160df38af0eac26c42324 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 6 Nov 2023 17:48:48 +0100 Subject: [PATCH 26/57] feat(IntegrationDatasetLocks): integrate locks userPersistentId --- src/dataset/domain/models/Dataset.ts | 19 ++++--- .../infrastructure/mappers/JSDatasetMapper.ts | 2 +- .../edit-dataset-menu/EditDatasetMenu.tsx | 7 ++- .../PublishDatasetMenu.tsx | 7 ++- .../SubmitForReviewButton.tsx | 7 ++- .../DatasetUploadFilesButton.tsx | 2 +- .../edit-files-menu/EditFilesMenu.tsx | 2 +- .../file-options-menu/FileOptionsMenu.tsx | 4 +- .../dataset/domain/models/DatasetMother.ts | 2 +- .../mappers/JSDatasetMapper.spec.ts | 53 ++++++++++++++++--- .../EditDatasetMenu.spec.tsx | 24 +++++++-- .../PublishDatasetMenu.spec.tsx | 42 +++++++++------ .../SubmitForReviewButton.spec.tsx | 45 ++++++++++------ .../DatasetJSDataverseRepository.spec.ts | 2 +- 14 files changed, 155 insertions(+), 63 deletions(-) diff --git a/src/dataset/domain/models/Dataset.ts b/src/dataset/domain/models/Dataset.ts index 497947c21..0c34de6b7 100644 --- a/src/dataset/domain/models/Dataset.ts +++ b/src/dataset/domain/models/Dataset.ts @@ -259,7 +259,7 @@ export interface DatasetPermissions { } export interface DatasetLock { - id: number + userPersistentId: string reason: DatasetLockReason } @@ -302,8 +302,8 @@ export class Dataset { return this.metadataBlocks[0].fields.title } - public get isLockedFromPublishing(): boolean { - return this.isLockedFromEdits + public checkIsLockedFromPublishing(userPersistentId: string): boolean { + return this.checkIsLockedFromEdits(userPersistentId) } public get isLocked(): boolean { @@ -314,12 +314,19 @@ export class Dataset { return this.locks.some((lock) => lock.reason === DatasetLockReason.WORKFLOW) } - public get isLockedFromEdits(): boolean { + public checkIsLockedFromEdits(userPersistentId: string): boolean { const lockedReasonIsInReview = this.locks.some( (lock) => lock.reason === DatasetLockReason.IN_REVIEW ) - // If the lock reason is workflow and the workflow userId is the same as the current user, then the user can edit - // TODO - Ask how we want to manage pending workflows + + if ( + this.locks.some( + (lock) => + lock.reason === DatasetLockReason.WORKFLOW && lock.userPersistentId === userPersistentId + ) + ) { + return false + } return this.isLocked && !(lockedReasonIsInReview && this.permissions.canPublishDataset) } diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index 049055037..d4475c287 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -193,7 +193,7 @@ export class JSDatasetMapper { static toLocks(jsDatasetLocks: JSDatasetLock[]): DatasetLock[] { return jsDatasetLocks.map((jsDatasetLock) => { return { - id: 0, + userPersistentId: jsDatasetLock.userId, reason: jsDatasetLock.lockType as unknown as DatasetLockReason } }) 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..798e2d451 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,13 +4,16 @@ import { EditDatasetPermissionsMenu } from './EditDatasetPermissionsMenu' import { DeleteDatasetButton } from './DeleteDatasetButton' import { DeaccessionDatasetButton } from './DeaccessionDatasetButton' import { useTranslation } from 'react-i18next' +import { useSession } from '../../../session/SessionContext' interface EditDatasetMenuProps { dataset: Dataset } export function EditDatasetMenu({ dataset }: EditDatasetMenuProps) { - if (!dataset.permissions.canUpdateDataset) { + const { user } = useSession() + + if (!user || !dataset.permissions.canUpdateDataset) { return <> } @@ -21,7 +24,7 @@ export function EditDatasetMenu({ dataset }: EditDatasetMenuProps) { title={t('datasetActionButtons.editDataset.title')} asButtonGroup variant="secondary" - disabled={dataset.isLockedFromEdits}> + disabled={dataset.checkIsLockedFromEdits(user.persistentId)}> {t('datasetActionButtons.editDataset.filesUpload')} 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..ac7f9cc0d 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,15 +2,18 @@ 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 { useSession } from '../../../session/SessionContext' interface PublishDatasetMenuProps { dataset: Dataset } export function PublishDatasetMenu({ dataset }: PublishDatasetMenuProps) { + const { user } = useSession() if ( !dataset.version.isLatest || dataset.version.publishingStatus !== DatasetPublishingStatus.DRAFT || + !user || !dataset.permissions.canPublishDataset ) { return <> @@ -24,7 +27,9 @@ export function PublishDatasetMenu({ dataset }: PublishDatasetMenuProps) { asButtonGroup variant="secondary" disabled={ - dataset.isLockedFromPublishing || !dataset.hasValidTermsOfAccess || !dataset.isValid + dataset.checkIsLockedFromPublishing(user.persistentId) || + !dataset.hasValidTermsOfAccess || + !dataset.isValid }> {t('datasetActionButtons.publish.publish')} {dataset.version.isInReview && ( diff --git a/src/sections/dataset/dataset-action-buttons/submit-for-review-button/SubmitForReviewButton.tsx b/src/sections/dataset/dataset-action-buttons/submit-for-review-button/SubmitForReviewButton.tsx index 1f9c18a3a..b352b4065 100644 --- a/src/sections/dataset/dataset-action-buttons/submit-for-review-button/SubmitForReviewButton.tsx +++ b/src/sections/dataset/dataset-action-buttons/submit-for-review-button/SubmitForReviewButton.tsx @@ -1,17 +1,20 @@ import { Dataset, DatasetPublishingStatus } from '../../../../dataset/domain/models/Dataset' import { Button } from '@iqss/dataverse-design-system' import { useTranslation } from 'react-i18next' +import { useSession } from '../../../session/SessionContext' interface SubmitForReviewButtonProps { dataset: Dataset } export function SubmitForReviewButton({ dataset }: SubmitForReviewButtonProps) { + const { user } = useSession() if ( !dataset.version.isLatest || dataset.version.publishingStatus !== DatasetPublishingStatus.DRAFT || dataset.isLockedInWorkflow || dataset.permissions.canPublishDataset || + !user || !dataset.permissions.canUpdateDataset ) { return <> @@ -22,7 +25,9 @@ export function SubmitForReviewButton({ dataset }: SubmitForReviewButtonProps) { ) diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.tsx index 052d5957e..23f34bd02 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.tsx @@ -30,7 +30,7 @@ export function EditFilesMenu({ files, fileSelection }: EditFilesMenuProps) { variant="secondary" id="edit-files-menu" title={t('actions.editFilesMenu.title')} - disabled={dataset.isLockedFromEdits || !dataset.hasValidTermsOfAccess} + disabled={dataset.checkIsLockedFromEdits(user.persistentId) || !dataset.hasValidTermsOfAccess} icon={}> diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/FileOptionsMenu.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/FileOptionsMenu.tsx index c81d34c77..f336fe594 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/FileOptionsMenu.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/FileOptionsMenu.tsx @@ -24,7 +24,7 @@ export function FileOptionsMenu({ file }: { file: File }) { {t('actions.optionsMenu.title')}}>