From a21127a56611febc19ac1874ddf0ca93315b895b Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 31 Jul 2023 12:23:45 +0100 Subject: [PATCH 01/21] Added: beta testing deployment GitHub action --- .github/workflows/deploy-beta-testing.yml | 98 +++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 .github/workflows/deploy-beta-testing.yml diff --git a/.github/workflows/deploy-beta-testing.yml b/.github/workflows/deploy-beta-testing.yml new file mode 100644 index 000000000..b7161b6e9 --- /dev/null +++ b/.github/workflows/deploy-beta-testing.yml @@ -0,0 +1,98 @@ +name: 'Deploy to Beta Testing' + +on: + push: + branches: + - develop + +jobs: + build: + runs-on: ubuntu-latest + environment: beta-testing + + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + + - name: Create .npmrc + run: | + cp .npmrc.example .npmrc + sed -i -e 's//${{ secrets.GITHUB_TOKEN }}/g' .npmrc + sed -i -e 's//${{ secrets.NPM_AUTH_TOKEN }}/g' .npmrc + + - name: Install Dependencies + run: npm install + + - name: Build Dataverse UI Library + working-directory: packages/design-system + run: npm run build + + - name: Create and populate .env file + env: + DATAVERSE_BACKEND_URL: ${{ secrets.DATAVERSE_BACKEND_URL }} + run: | + touch .env + echo VITE_DATAVERSE_BACKEND_URL="$DATAVERSE_BACKEND_URL" >> .env + shell: bash + + - name: Build with base path + run: npm run build -- --base=/spa + + - uses: actions/upload-artifact@v3 + with: + name: built-site + path: ./dist + + deploy-to-payara: + needs: build + runs-on: ubuntu-latest + environment: beta-testing + + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '11' + + - uses: actions/download-artifact@v3 + with: + name: built-site + path: ./dist + + - name: Get branch name + id: branch-name + uses: tj-actions/branch-names@v6 + + - name: Build war file + working-directory: ./deployment/payara + run: mvn package "-Dversion=${{ steps.branch-name.outputs.current_branch }}" + + - name: Copy war file to remote instance + uses: appleboy/scp-action@master + with: + host: ${{ secrets.PAYARA_INSTANCE_HOST }} + username: ${{ secrets.PAYARA_INSTANCE_USERNAME }} + key: ${{ secrets.PAYARA_INSTANCE_SSH_PRIVATE_KEY }} + source: './deployment/payara/target/dataverse-frontend.war' + target: '/home/${{ secrets.PAYARA_INSTANCE_USERNAME }}' + overwrite: true + + - name: Execute payara war deployment remotely + uses: appleboy/ssh-action@v0.1.8 + with: + host: ${{ secrets.PAYARA_INSTANCE_HOST }} + username: ${{ secrets.PAYARA_INSTANCE_USERNAME }} + key: ${{ secrets.PAYARA_INSTANCE_SSH_PRIVATE_KEY }} + script: | + APPLICATION_NAME=dataverse-frontend + APPLICATION_WAR_PATH=deployment/payara/target/$APPLICATION_NAME.war + ASADMIN='/usr/local/payara5/bin/asadmin --user admin' + DATAVERSE_FRONTEND=`$ASADMIN list-applications |grep $APPLICATION_NAME |awk '{print $1}'` + $ASADMIN undeploy $DATAVERSE_FRONTEND + $ASADMIN deploy --name $APPLICATION_NAME --contextroot /spa $APPLICATION_WAR_PATH From a20339226ec35757da207c9d5b6f9cfe02ee5bc8 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 31 Jul 2023 12:38:16 +0100 Subject: [PATCH 02/21] Added: README section about Beta Testing Environment --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index c689fccac..a119c068a 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,19 @@ It is important that the remote instance is correctly pre-configured, with the P A base path for the frontend application can be established on the remote server by setting the corresponding field in the workflow inputs. This mechanism prevents conflicts between the frontend application and any pre-existing deployed application running on Payara, which can potentially be a Dataverse backend. This way, only the routes with the base path included will redirect to the frontend application. +#### Beta Testing Environment + +To make the SPA Frontend accesible and testable by people interested in the project, there is a remote beta testing environment that includes the latest changes developed both for the frontend application and the Dataverse backend application (develop branches). + +This environment follows the "all-in-one" solution described above, where both applications coexist on a Payara server. + +Environment updates are carried out automatically through GitHub actions, present both in this repository and in the Dataverse backend repository, which deploy the develop branches when any change is pushed to them. + +The environment is accessible through the following URLs: + +- SPA: http://ec2-3-210-184-82.compute-1.amazonaws.com/spa +- JSF: http://ec2-3-210-184-82.compute-1.amazonaws.com + ## Changes from the Style Guide The design system and frontend in this repo are inspired by the Dataverse Project [Style Guide](https://guides.dataverse.org/en/latest/style/index.html), but the following changes have been made, especially for accessibility. From d12f01a5abd0737c66c20bf8d3165def896c3884 Mon Sep 17 00:00:00 2001 From: Guillermo Portas Date: Wed, 2 Aug 2023 16:00:07 +0100 Subject: [PATCH 03/21] Changed: updated beta testing environment URL in the README Co-authored-by: Philip Durbin --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a119c068a..efb3c33db 100644 --- a/README.md +++ b/README.md @@ -190,8 +190,8 @@ Environment updates are carried out automatically through GitHub actions, presen The environment is accessible through the following URLs: -- SPA: http://ec2-3-210-184-82.compute-1.amazonaws.com/spa -- JSF: http://ec2-3-210-184-82.compute-1.amazonaws.com +- SPA: https://beta.dataverse.org/spa +- JSF: https://beta.dataverse.org ## Changes from the Style Guide From 01fb15bdccbc022cee072d079163f621d7c11e3a Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Fri, 4 Aug 2023 11:05:48 -0400 Subject: [PATCH 04/21] add demo video to README #132 --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index efb3c33db..eecad2e5f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ [![Tests](https://github.com/IQSS/dataverse-frontend/actions/workflows/test.yml/badge.svg)](https://github.com/IQSS/dataverse-frontend/actions/workflows/test.yml) [![Unit Tests Coverage](https://coveralls.io/repos/github/IQSS/dataverse-frontend/badge.svg?branch=develop)](https://coveralls.io/github/IQSS/dataverse-frontend?branch=develop) +## Demo videos + +- 2023-08-01: [View mode of the dataset page](https://groups.google.com/g/dataverse-community/c/cxZ3Bal_-uo/m/h3kh3iVNCwAJ) + ## Getting Started First install node >=16 and npm >=8. Recommended versions `node v19` and `npm v9`. From be64802b2aa697b89dbbe5b51d42d66a9a3f04be Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 7 Aug 2023 12:08:18 +0200 Subject: [PATCH 05/21] fix: update js-dataverse version --- dev-env/run-env.sh | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- .../repositories/DatasetJSDataverseRepository.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dev-env/run-env.sh b/dev-env/run-env.sh index a8a2f09cd..28ebd20c6 100755 --- a/dev-env/run-env.sh +++ b/dev-env/run-env.sh @@ -6,7 +6,7 @@ export DATAVERSE_IMAGE_TAG=$1 export COMPOSE_HTTP_TIMEOUT=200 # Timeout for Dataverse bootstrap configbaker -export DATAVERSE_BOOTSTRAP_TIMEOUT="5m" +export DATAVERSE_BOOTSTRAP_TIMEOUT="10m" echo "INFO - Setting up Dataverse on image tag ${DATAVERSE_IMAGE_TAG}..." diff --git a/package-lock.json b/package-lock.json index 1c3447df2..96d02242d 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-pr67.5dfc70a", + "@iqss/dataverse-client-javascript": "2.0.0-pr80.378cc1e", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -3700,9 +3700,9 @@ }, "node_modules/@iqss/dataverse-client-javascript": { "name": "@IQSS/dataverse-client-javascript", - "version": "2.0.0-pr67.5dfc70a", - "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr67.5dfc70a/dc27a9a220a347fb9e5d87f6887c8e87427482c1", - "integrity": "sha512-RCBJcdv42hYvWuLDE19/AYpKZr1AL9/VIre4FJm0vd0NMd1CLoPyDjetuK70nkP8fDDWVJLTlyaer8f1RZAfSA==", + "version": "2.0.0-pr80.378cc1e", + "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr80.378cc1e/2361fc91f5e8618751025871e860062307b1adf4", + "integrity": "sha512-5Omv7ZG+K+B8ddEg0X3MjqbAK/cOPnPtclpBe2sptYRH1zaX1kHwSSTTBDmfNfIZTJUOe7KEP0/FX6Xa6iRG6w==", "license": "MIT", "dependencies": { "@types/node": "^18.15.11", diff --git a/package.json b/package.json index 2b456b355..6c74c74f0 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-pr67.5dfc70a", + "@iqss/dataverse-client-javascript": "2.0.0-pr80.378cc1e", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", diff --git a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts index 206719395..ea25c0aa2 100644 --- a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts +++ b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts @@ -1,7 +1,7 @@ import { DatasetRepository } from '../../domain/repositories/DatasetRepository' import { Dataset } from '../../domain/models/Dataset' import { - getDatasetByPersistentId, + getDataset, getDatasetCitation, getDatasetSummaryFieldNames, WriteError, @@ -13,7 +13,7 @@ import { JSDatasetMapper } from '../mappers/JSDatasetMapper' export class DatasetJSDataverseRepository implements DatasetRepository { getByPersistentId(persistentId: string, version?: string): Promise { - return getDatasetByPersistentId + return getDataset .execute(persistentId, this.versionToVersionId(version)) .then((jsDataset) => Promise.all([ From ff963f687a42b07e04bc157b44245db141542f3c Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 8 Aug 2023 09:55:50 +0200 Subject: [PATCH 06/21] feat(MetadataBlockInfo): integrate js-dataverse implementation to getMetadataBlockInfo --- .../domain/models/MetadataBlockInfo.ts | 5 +- .../MetadataBlockInfoJSDataverseRepository.ts | 31 ------- .../mappers/JSMetadataBlockInfoMapper.ts | 51 +++++++++++ .../MetadataBlockInfoJSDataverseRepository.ts | 17 ++++ src/sections/dataset/DatasetFactory.tsx | 2 +- .../DatasetMetadataFieldValue.tsx | 83 ++--------------- .../DatasetMetadataFieldValueFormatted.tsx | 90 +++++++++++++++++++ .../dataset/domain/models/DatasetMother.ts | 11 ++- .../domain/models/MetadataBlockInfoMother.ts | 7 +- .../dataset-metadata/DatasetMetadata.spec.tsx | 61 +++++++++++-- .../dataset-summary/SummaryFields.spec.tsx | 6 +- .../MetadataBlockInfoCitationExample.ts | 83 +++++++++++++++++ ...dataBlockInfoJSDataverseRepository.spec.ts | 25 ++++++ 13 files changed, 346 insertions(+), 126 deletions(-) delete mode 100644 src/metadata-block-info/infrastructure/MetadataBlockInfoJSDataverseRepository.ts create mode 100644 src/metadata-block-info/infrastructure/mappers/JSMetadataBlockInfoMapper.ts create mode 100644 src/metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository.ts create mode 100644 src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValueFormatted.tsx create mode 100644 tests/e2e-integration/integration/metadata-block-info/MetadataBlockInfoCitationExample.ts create mode 100644 tests/e2e-integration/integration/metadata-block-info/MetadataBlockInfoJSDataverseRepository.spec.ts diff --git a/src/metadata-block-info/domain/models/MetadataBlockInfo.ts b/src/metadata-block-info/domain/models/MetadataBlockInfo.ts index bffa2bcc8..7fa9e53a3 100644 --- a/src/metadata-block-info/domain/models/MetadataBlockInfo.ts +++ b/src/metadata-block-info/domain/models/MetadataBlockInfo.ts @@ -1,10 +1,13 @@ export interface MetadataBlockInfo { name: string - fields: Record + fields: MetadataBlockInfoFields } +export type MetadataBlockInfoFields = Record + export interface MetadataFieldInfo { displayFormat: string } export const METADATA_FIELD_DISPLAY_FORMAT_PLACEHOLDER = '#VALUE' +export const METADATA_FIELD_DISPLAY_FORMAT_NAME_PLACEHOLDER = '#NAME' diff --git a/src/metadata-block-info/infrastructure/MetadataBlockInfoJSDataverseRepository.ts b/src/metadata-block-info/infrastructure/MetadataBlockInfoJSDataverseRepository.ts deleted file mode 100644 index 6ce2e94c4..000000000 --- a/src/metadata-block-info/infrastructure/MetadataBlockInfoJSDataverseRepository.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { MetadataBlockInfoRepository } from '../domain/repositories/MetadataBlockInfoRepository' -import { MetadataBlockInfo } from '../domain/models/MetadataBlockInfo' -import { MetadataBlockName } from '../../dataset/domain/models/Dataset' - -export class MetadataBlockInfoJSDataverseRepository implements MetadataBlockInfoRepository { - // eslint-disable-next-line unused-imports/no-unused-vars - getByName(name: string): Promise { - // TODO implement using js-dataverse - return Promise.resolve({ - name: MetadataBlockName.CITATION, - fields: { - alternativePersistentId: { displayFormat: '' }, - publicationDate: { displayFormat: '' }, - citationDate: { displayFormat: '' }, - title: { displayFormat: '' }, - subject: { displayFormat: ';' }, - author: { displayFormat: '' }, - authorName: { displayFormat: '#VALUE' }, - authorAffiliation: { displayFormat: '(#VALUE)' }, - authorIdentifierScheme: { displayFormat: '- #VALUE:' }, - authorIdentifier: { displayFormat: '#VALUE' }, - datasetContact: { displayFormat: '#VALUE' }, - datasetContactName: { displayFormat: '#VALUE' }, - datasetContactAffiliation: { displayFormat: '(#VALUE)' }, - datasetContactEmail: { displayFormat: '[#VALUE](mailto:#VALUE)' }, - dsDescription: { displayFormat: '' }, - dsDescriptionValue: { displayFormat: '#VALUE' } - } - }) - } -} diff --git a/src/metadata-block-info/infrastructure/mappers/JSMetadataBlockInfoMapper.ts b/src/metadata-block-info/infrastructure/mappers/JSMetadataBlockInfoMapper.ts new file mode 100644 index 000000000..d345c9c66 --- /dev/null +++ b/src/metadata-block-info/infrastructure/mappers/JSMetadataBlockInfoMapper.ts @@ -0,0 +1,51 @@ +import { + MetadataBlock as JSMetadataBlockInfo, + MetadataFieldInfo as JSMetadataFieldInfo +} from '@iqss/dataverse-client-javascript' +import { MetadataBlockInfo, MetadataBlockInfoFields } from '../../domain/models/MetadataBlockInfo' + +export class JSMetadataBlockInfoMapper { + static toMetadataBlockInfo(jsMetadataBlockInfo: JSMetadataBlockInfo): MetadataBlockInfo { + return { + name: jsMetadataBlockInfo.name, + fields: this.toFields(jsMetadataBlockInfo.metadataFields) + } + } + + static toFields( + jsMetadataBlockInfoFields: Record + ): MetadataBlockInfoFields { + return Object.entries(jsMetadataBlockInfoFields).reduce( + (fields: MetadataBlockInfoFields, [key, value]) => { + fields[key] = { displayFormat: this.toDisplayFormat(value.displayFormat) } + return fields + }, + {} + ) + } + + static toDisplayFormat(jsDisplayFormat: string): string { + const link = 'href="#VALUE"' + if (jsDisplayFormat.includes(link)) { + return '[#VALUE](#VALUE)' + } + + const linkWithUrl = /]*>#VALUE<\/a>/ + const match = jsDisplayFormat.match(linkWithUrl) + if (match) { + return `[#VALUE](${match[1]}/#VALUE)` + } + + const emailFormat = '#EMAIL' + if (jsDisplayFormat === emailFormat) { + return '[#VALUE](mailto:#VALUE)' + } + + const imageFormat = '
' + if (jsDisplayFormat === imageFormat) { + return '![#NAME](#VALUE)' + } + + return jsDisplayFormat + } +} diff --git a/src/metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository.ts b/src/metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository.ts new file mode 100644 index 000000000..38e5bb4d4 --- /dev/null +++ b/src/metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository.ts @@ -0,0 +1,17 @@ +import { MetadataBlockInfoRepository } from '../../domain/repositories/MetadataBlockInfoRepository' +import { MetadataBlockInfo } from '../../domain/models/MetadataBlockInfo' +import { + getMetadataBlockByName, + MetadataBlock as JSMetadataBlockInfo +} from '@iqss/dataverse-client-javascript' +import { JSMetadataBlockInfoMapper } from '../mappers/JSMetadataBlockInfoMapper' + +export class MetadataBlockInfoJSDataverseRepository implements MetadataBlockInfoRepository { + getByName(name: string): Promise { + return getMetadataBlockByName + .execute(name) + .then((jsMetadataBlockInfo: JSMetadataBlockInfo) => + JSMetadataBlockInfoMapper.toMetadataBlockInfo(jsMetadataBlockInfo) + ) + } +} diff --git a/src/sections/dataset/DatasetFactory.tsx b/src/sections/dataset/DatasetFactory.tsx index 875485217..e90f0e0ad 100644 --- a/src/sections/dataset/DatasetFactory.tsx +++ b/src/sections/dataset/DatasetFactory.tsx @@ -6,7 +6,7 @@ import { useAnonymized } from './anonymized/AnonymizedContext' import { AnonymizedProvider } from './anonymized/AnonymizedProvider' import { FileJSDataverseRepository } from '../../files/infrastructure/FileJSDataverseRepository' import { MetadataBlockInfoProvider } from './metadata-block-info/MetadataBlockProvider' -import { MetadataBlockInfoJSDataverseRepository } from '../../metadata-block-info/infrastructure/MetadataBlockInfoJSDataverseRepository' +import { MetadataBlockInfoJSDataverseRepository } from '../../metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository' const datasetRepository = new DatasetJSDataverseRepository() const fileRepository = new FileJSDataverseRepository() diff --git a/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValue.tsx b/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValue.tsx index 1722b4159..99f4fe514 100644 --- a/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValue.tsx +++ b/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValue.tsx @@ -1,16 +1,10 @@ import { ANONYMIZED_FIELD_VALUE, - DatasetMetadataFieldValue as DatasetMetadataFieldValueModel, - DatasetMetadataSubField + DatasetMetadataFieldValue as DatasetMetadataFieldValueModel } from '../../../../dataset/domain/models/Dataset' -import { MarkdownComponent } from '../../markdown/MarkdownComponent' import { useAnonymized } from '../../anonymized/AnonymizedContext' import { useTranslation } from 'react-i18next' -import { useMetadataBlockInfo } from '../../metadata-block-info/MetadataBlockInfoContext' -import { - METADATA_FIELD_DISPLAY_FORMAT_PLACEHOLDER, - MetadataBlockInfo -} from '../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { DatasetMetadataFieldValueFormatted } from './DatasetMetadataFieldValueFormatted' interface DatasetMetadataFieldValueProps { metadataFieldName: string @@ -23,19 +17,14 @@ export function DatasetMetadataFieldValue({ }: DatasetMetadataFieldValueProps) { const { anonymizedView } = useAnonymized() const isAnonymizedField = anonymizedView && metadataFieldValue == ANONYMIZED_FIELD_VALUE - const { metadataBlockInfo } = useMetadataBlockInfo() - if (isAnonymizedField) { return } return ( - ) } @@ -44,65 +33,3 @@ const AnonymizedFieldValue = () => { const { t } = useTranslation('dataset') return

{t('anonymizedFieldValue')}

} - -export function metadataFieldValueToString( - metadataFieldName: string, - metadataFieldValue: DatasetMetadataFieldValueModel, - metadataBlockInfo?: MetadataBlockInfo -): string { - const separator = metadataBlockInfo?.fields[metadataFieldName]?.displayFormat ?? '' - - if (isArrayOfObjects(metadataFieldValue)) { - return metadataFieldValue - .map((metadataSubField) => joinSubFields(metadataSubField, metadataBlockInfo)) - .join(' \n \n') - } - - if (Array.isArray(metadataFieldValue)) { - return metadataFieldValue.join(`${separator} `) - } - - if (isAnObject(metadataFieldValue)) { - return Object.values(metadataFieldValue).join(`${separator} `) - } - - return metadataFieldValue -} - -export function isArrayOfObjects(variable: unknown): variable is object[] { - if (!Array.isArray(variable)) { - return false - } - - return variable.every((item) => isAnObject(item)) -} - -function isAnObject(variable: unknown): variable is object { - return typeof variable === 'object' && variable !== null -} - -function joinSubFields( - metadataSubField: DatasetMetadataSubField, - metadataBlockInfo?: MetadataBlockInfo -) { - return Object.entries(metadataSubField) - .map(([subFieldName, subFieldValue]) => - formatSubFieldValue(subFieldValue, metadataBlockInfo?.fields[subFieldName]?.displayFormat) - ) - .join(' ') -} - -function formatSubFieldValue( - subFieldValue: string | undefined, - displayFormat: string | undefined -): string { - if (subFieldValue === undefined) { - return '' - } - - if (displayFormat === undefined) { - return subFieldValue - } - - return displayFormat.replaceAll(METADATA_FIELD_DISPLAY_FORMAT_PLACEHOLDER, subFieldValue) -} diff --git a/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValueFormatted.tsx b/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValueFormatted.tsx new file mode 100644 index 000000000..c0bc09b14 --- /dev/null +++ b/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValueFormatted.tsx @@ -0,0 +1,90 @@ +import { useMetadataBlockInfo } from '../../metadata-block-info/MetadataBlockInfoContext' +import { + METADATA_FIELD_DISPLAY_FORMAT_PLACEHOLDER, + MetadataBlockInfo +} from '../../../../metadata-block-info/domain/models/MetadataBlockInfo' +import { MarkdownComponent } from '../../markdown/MarkdownComponent' +import { + DatasetMetadataFieldValue as DatasetMetadataFieldValueModel, + DatasetMetadataSubField +} from '../../../../dataset/domain/models/Dataset' + +interface DatasetMetadataFieldValueFormattedProps { + metadataFieldName: string + metadataFieldValue: DatasetMetadataFieldValueModel +} +export function DatasetMetadataFieldValueFormatted({ + metadataFieldName, + metadataFieldValue +}: DatasetMetadataFieldValueFormattedProps) { + const { metadataBlockInfo } = useMetadataBlockInfo() + const metadataFieldValueMarkdown = metadataFieldValueToMarkdownFormat( + metadataFieldName, + metadataFieldValue, + metadataBlockInfo + ) + + return +} + +export function metadataFieldValueToMarkdownFormat( + metadataFieldName: string, + metadataFieldValue: DatasetMetadataFieldValueModel, + metadataBlockInfo?: MetadataBlockInfo +): string { + const separator = metadataBlockInfo?.fields[metadataFieldName]?.displayFormat ?? '' + + if (isArrayOfObjects(metadataFieldValue)) { + return metadataFieldValue + .map((metadataSubField) => joinSubFields(metadataSubField, metadataBlockInfo)) + .join(' \n \n') + } + + if (Array.isArray(metadataFieldValue)) { + return metadataFieldValue.join(`${separator} `) + } + + if (isAnObject(metadataFieldValue)) { + return joinObjectValues(metadataFieldValue, separator) + } + + return metadataFieldValue +} + +export function isArrayOfObjects(variable: unknown): variable is object[] { + return Array.isArray(variable) && variable.every(isAnObject) +} + +function isAnObject(variable: unknown): variable is object { + return typeof variable === 'object' && variable !== null +} + +function joinObjectValues(obj: object, separator: string): string { + return Object.values(obj).join(separator) +} + +function joinSubFields( + metadataSubField: DatasetMetadataSubField, + metadataBlockInfo?: MetadataBlockInfo +): string { + return Object.entries(metadataSubField) + .map(([subFieldName, subFieldValue]) => + formatSubFieldValue(subFieldValue, metadataBlockInfo?.fields[subFieldName]?.displayFormat) + ) + .join(' ') +} + +function formatSubFieldValue( + subFieldValue: string | undefined, + displayFormat: string | undefined +): string { + if (subFieldValue === undefined) { + return '' + } + + if (displayFormat === undefined) { + return subFieldValue + } + + return displayFormat.replaceAll(METADATA_FIELD_DISPLAY_FORMAT_PLACEHOLDER, subFieldValue) +} diff --git a/tests/component/dataset/domain/models/DatasetMother.ts b/tests/component/dataset/domain/models/DatasetMother.ts index 0e84fa720..4030c52c0 100644 --- a/tests/component/dataset/domain/models/DatasetMother.ts +++ b/tests/component/dataset/domain/models/DatasetMother.ts @@ -68,13 +68,13 @@ export class DatasetMother { authorName: faker.lorem.sentence(), authorAffiliation: faker.lorem.sentence(), authorIdentifierScheme: faker.lorem.sentence(), - authorIdentifier: faker.lorem.sentence() + authorIdentifier: faker.lorem.word() }, { authorName: faker.lorem.sentence(), authorAffiliation: faker.lorem.sentence(), authorIdentifierScheme: faker.lorem.sentence(), - authorIdentifier: faker.lorem.sentence() + authorIdentifier: faker.lorem.word() } ], datasetContact: [ @@ -87,6 +87,13 @@ export class DatasetMother { { dsDescriptionValue: faker.lorem.sentence() } + ], + producer: [ + { + producerName: faker.lorem.sentence(), + producerURL: faker.internet.url(), + producerLogoURL: faker.image.imageUrl() + } ] } }, diff --git a/tests/component/metadata-block-info/domain/models/MetadataBlockInfoMother.ts b/tests/component/metadata-block-info/domain/models/MetadataBlockInfoMother.ts index 791b5b9a1..c31293d05 100644 --- a/tests/component/metadata-block-info/domain/models/MetadataBlockInfoMother.ts +++ b/tests/component/metadata-block-info/domain/models/MetadataBlockInfoMother.ts @@ -19,9 +19,12 @@ export class MetadataBlockInfoMother { datasetContact: { displayFormat: '#VALUE' }, datasetContactName: { displayFormat: '#VALUE' }, datasetContactAffiliation: { displayFormat: '(#VALUE)' }, - datasetContactEmail: { displayFormat: '#VALUE' }, + datasetContactEmail: { displayFormat: '[#VALUE](mailto:#VALUE)' }, dsDescription: { displayFormat: '' }, - dsDescriptionValue: { displayFormat: '#VALUE' } + dsDescriptionValue: { displayFormat: '#VALUE' }, + producerURL: { displayFormat: '[#VALUE](#VALUE)' }, + producerLogoURL: { displayFormat: '![#NAME](#VALUE)' }, + dateOfCollectionStart: { displayFormat: '#NAME: #VALUE ' } }, ...props } diff --git a/tests/component/sections/dataset/dataset-metadata/DatasetMetadata.spec.tsx b/tests/component/sections/dataset/dataset-metadata/DatasetMetadata.spec.tsx index 7cd582a20..f4718182a 100644 --- a/tests/component/sections/dataset/dataset-metadata/DatasetMetadata.spec.tsx +++ b/tests/component/sections/dataset/dataset-metadata/DatasetMetadata.spec.tsx @@ -7,13 +7,61 @@ import { import { AnonymizedContext } from '../../../../../src/sections/dataset/anonymized/AnonymizedContext' import { isArrayOfObjects, - metadataFieldValueToString -} from '../../../../../src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValue' + metadataFieldValueToMarkdownFormat +} from '../../../../../src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValueFormatted' import { MetadataBlockInfoProvider } from '../../../../../src/sections/dataset/metadata-block-info/MetadataBlockProvider' import { MetadataBlockInfoRepository } from '../../../../../src/metadata-block-info/domain/repositories/MetadataBlockInfoRepository' import { MetadataBlockInfoMother } from '../../../metadata-block-info/domain/models/MetadataBlockInfoMother' +const extractLinksFromText = (text: string): { text: string; link: string }[] => { + const linkFormat = /(? { + const matchResult = match.match(/\[(.*?)\]\((.*?)\)/) + if (matchResult) { + const [, text, link] = matchResult + return { text, link } + } + return null + }) + .filter((match) => match !== null) as { text: string; link: string }[] +} + +const extractImagesFromText = (text: string): string[] => { + return text.match(/!\[(.*?)\]\((.*?)\)/g) || [] +} + describe('DatasetMetadata', () => { + const checkMetadataFieldValue = (metadataFieldName: string, metadataFieldValue: string) => { + const extractedLinks = extractLinksFromText(metadataFieldValue) + const extractedImages = extractImagesFromText(metadataFieldValue) + const notPlainText = extractedLinks.length > 0 || extractedImages + + if (notPlainText) { + if (extractedLinks) { + extractedLinks.forEach(({ text, link }) => { + cy.findByText(text).should('exist') + cy.findByText(text).should('have.attr', 'href', link) + }) + } + if (extractedImages) { + extractedImages.forEach((image) => { + const [, altText, imageUrl] = image.match(/!\[(.*?)\]\((.*?)\)/) || [] + cy.findByAltText(altText).should('exist') + cy.findByAltText(altText).should('have.attr', 'src', imageUrl) + }) + } + } else { + cy.findByText(metadataFieldValue).should('exist') + } + } + it('renders the metadata blocks sections titles correctly', () => { const mockDataset = DatasetMother.create() const mockMetadataBlocks = mockDataset.metadataBlocks @@ -130,7 +178,7 @@ describe('DatasetMetadata', () => { } Object.entries(metadataBlock.fields).forEach(([metadataFieldName, metadataFieldValue]) => { - const metadataFieldValueString = metadataFieldValueToString( + const metadataFieldValueString = metadataFieldValueToMarkdownFormat( metadataFieldName, metadataFieldValue, metadataBlockInfoMock @@ -138,15 +186,12 @@ describe('DatasetMetadata', () => { if (isArrayOfObjects(metadataFieldValue)) { metadataFieldValueString.split(' \n \n').forEach((fieldValue) => { - cy.findAllByText(fieldValue).should('exist') + checkMetadataFieldValue(metadataFieldName, fieldValue) }) return } - const fieldValue = cy.findAllByText(metadataFieldValueString, { - exact: false - }) - fieldValue.should('exist') + checkMetadataFieldValue(metadataFieldName, metadataFieldValueString) }) }) }) diff --git a/tests/component/sections/dataset/dataset-summary/SummaryFields.spec.tsx b/tests/component/sections/dataset/dataset-summary/SummaryFields.spec.tsx index 4124e08b1..1682e93b2 100644 --- a/tests/component/sections/dataset/dataset-summary/SummaryFields.spec.tsx +++ b/tests/component/sections/dataset/dataset-summary/SummaryFields.spec.tsx @@ -3,8 +3,8 @@ import { SummaryFields } from '../../../../../src/sections/dataset/dataset-summa import { DatasetMother } from '../../../dataset/domain/models/DatasetMother' import { isArrayOfObjects, - metadataFieldValueToString -} from '../../../../../src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValue' + metadataFieldValueToMarkdownFormat +} from '../../../../../src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValueFormatted' import { MetadataBlockInfoMother } from '../../../metadata-block-info/domain/models/MetadataBlockInfoMother' import { MetadataBlockInfoRepository } from '../../../../../src/metadata-block-info/domain/repositories/MetadataBlockInfoRepository' import { MetadataBlockInfoProvider } from '../../../../../src/sections/dataset/metadata-block-info/MetadataBlockProvider' @@ -38,7 +38,7 @@ describe('DatasetSummary', () => { ) summaryFieldDescription.should('exist') - const summaryFieldValueString = metadataFieldValueToString( + const summaryFieldValueString = metadataFieldValueToMarkdownFormat( summaryFieldName, summaryFieldValue, metadataBlockInfoMock diff --git a/tests/e2e-integration/integration/metadata-block-info/MetadataBlockInfoCitationExample.ts b/tests/e2e-integration/integration/metadata-block-info/MetadataBlockInfoCitationExample.ts new file mode 100644 index 000000000..86ae7db65 --- /dev/null +++ b/tests/e2e-integration/integration/metadata-block-info/MetadataBlockInfoCitationExample.ts @@ -0,0 +1,83 @@ +export const MetadataBlockInfoCitationExample = { + name: 'citation', + fields: { + title: { displayFormat: '' }, + subtitle: { displayFormat: '' }, + alternativeTitle: { displayFormat: '' }, + alternativeURL: { displayFormat: '[#VALUE](#VALUE)' }, + otherId: { displayFormat: ':' }, + otherIdAgency: { displayFormat: '#VALUE' }, + otherIdValue: { displayFormat: '#VALUE' }, + author: { displayFormat: '' }, + authorName: { displayFormat: '#VALUE' }, + authorAffiliation: { displayFormat: '(#VALUE)' }, + authorIdentifierScheme: { displayFormat: '- #VALUE:' }, + authorIdentifier: { displayFormat: '#VALUE' }, + datasetContact: { displayFormat: '' }, + datasetContactName: { displayFormat: '#VALUE' }, + datasetContactAffiliation: { displayFormat: '(#VALUE)' }, + datasetContactEmail: { displayFormat: '[#VALUE](mailto:#VALUE)' }, + dsDescription: { displayFormat: '' }, + dsDescriptionValue: { displayFormat: '#VALUE' }, + dsDescriptionDate: { displayFormat: '(#VALUE)' }, + subject: { displayFormat: '' }, + keyword: { displayFormat: '' }, + keywordValue: { displayFormat: '#VALUE' }, + keywordVocabulary: { displayFormat: '(#VALUE)' }, + keywordVocabularyURI: { displayFormat: '[#VALUE](#VALUE)' }, + topicClassification: { displayFormat: '' }, + topicClassValue: { displayFormat: '#VALUE' }, + topicClassVocab: { displayFormat: '(#VALUE)' }, + topicClassVocabURI: { displayFormat: '[#VALUE](#VALUE)' }, + publication: { displayFormat: '' }, + publicationCitation: { displayFormat: '#VALUE' }, + publicationIDType: { displayFormat: '#VALUE: ' }, + publicationIDNumber: { displayFormat: '#VALUE' }, + publicationURL: { displayFormat: '[#VALUE](#VALUE)' }, + notesText: { displayFormat: '' }, + language: { displayFormat: '' }, + producer: { displayFormat: '' }, + producerName: { displayFormat: '#VALUE' }, + producerAffiliation: { displayFormat: '(#VALUE)' }, + producerAbbreviation: { displayFormat: '(#VALUE)' }, + producerURL: { displayFormat: '[#VALUE](#VALUE)' }, + producerLogoURL: { displayFormat: '![#NAME](#VALUE)' }, + productionDate: { displayFormat: '' }, + productionPlace: { displayFormat: '' }, + contributor: { displayFormat: ':' }, + contributorType: { displayFormat: '#VALUE ' }, + contributorName: { displayFormat: '#VALUE' }, + grantNumber: { displayFormat: ':' }, + grantNumberAgency: { displayFormat: '#VALUE' }, + grantNumberValue: { displayFormat: '#VALUE' }, + distributor: { displayFormat: '' }, + distributorName: { displayFormat: '#VALUE' }, + distributorAffiliation: { displayFormat: '(#VALUE)' }, + distributorAbbreviation: { displayFormat: '(#VALUE)' }, + distributorURL: { displayFormat: '[#VALUE](#VALUE)' }, + distributorLogoURL: { displayFormat: '![#NAME](#VALUE)' }, + distributionDate: { displayFormat: '' }, + depositor: { displayFormat: '' }, + dateOfDeposit: { displayFormat: '' }, + timePeriodCovered: { displayFormat: ';' }, + timePeriodCoveredStart: { displayFormat: '#NAME: #VALUE ' }, + timePeriodCoveredEnd: { displayFormat: '#NAME: #VALUE ' }, + dateOfCollection: { displayFormat: ';' }, + dateOfCollectionStart: { displayFormat: '#NAME: #VALUE ' }, + dateOfCollectionEnd: { displayFormat: '#NAME: #VALUE ' }, + kindOfData: { displayFormat: '' }, + series: { displayFormat: ':' }, + seriesName: { displayFormat: '#VALUE' }, + seriesInformation: { displayFormat: '#VALUE' }, + software: { displayFormat: ',' }, + softwareName: { displayFormat: '#VALUE' }, + softwareVersion: { displayFormat: '#NAME: #VALUE' }, + relatedMaterial: { displayFormat: '' }, + relatedDatasets: { displayFormat: '' }, + otherReferences: { displayFormat: '' }, + dataSources: { displayFormat: '' }, + originOfSources: { displayFormat: '' }, + characteristicOfSources: { displayFormat: '' }, + accessToSources: { displayFormat: '' } + } +} diff --git a/tests/e2e-integration/integration/metadata-block-info/MetadataBlockInfoJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/metadata-block-info/MetadataBlockInfoJSDataverseRepository.spec.ts new file mode 100644 index 000000000..5eb1d0636 --- /dev/null +++ b/tests/e2e-integration/integration/metadata-block-info/MetadataBlockInfoJSDataverseRepository.spec.ts @@ -0,0 +1,25 @@ +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import { IntegrationTestsUtils } from '../IntegrationTestsUtils' +import { MetadataBlockInfoJSDataverseRepository } from '../../../../src/metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository' +import { MetadataBlockInfoCitationExample } from './MetadataBlockInfoCitationExample' + +chai.use(chaiAsPromised) +const expect = chai.expect + +const metadataBlockInfoExpected = MetadataBlockInfoCitationExample +const metadataBlockInfoRepository = new MetadataBlockInfoJSDataverseRepository() +describe('Metadata Block Info JSDataverse Repository', () => { + before(() => IntegrationTestsUtils.setup()) + beforeEach(() => IntegrationTestsUtils.login()) + + it('gets the metadataBlockInfo by name', async () => { + await metadataBlockInfoRepository.getByName('citation').then((metadataBlockInfo) => { + if (!metadataBlockInfo) { + throw new Error('Metadata Block Info not found') + } + + expect(metadataBlockInfo).to.deep.equal(metadataBlockInfoExpected) + }) + }) +}) From 127b6ea778ea932d136229f9d174f4d00c964419 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 8 Aug 2023 10:40:04 +0200 Subject: [PATCH 07/21] feat(MetadataBlockInfo): replace #NAME placeholder by the translated name --- .../DatasetMetadataField.tsx | 1 + .../DatasetMetadataFieldValue.tsx | 6 ++++- .../DatasetMetadataFieldValueFormatted.tsx | 18 ++++++++++--- .../dataset-metadata/DatasetMetadata.spec.tsx | 27 +++++++++++++------ .../dataset-summary/SummaryFields.spec.tsx | 4 +-- 5 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataField.tsx b/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataField.tsx index 71821add0..e8b6ef7a8 100644 --- a/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataField.tsx +++ b/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataField.tsx @@ -30,6 +30,7 @@ export function DatasetMetadataField({ metadataFieldName={metadataFieldName} /> diff --git a/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValue.tsx b/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValue.tsx index 99f4fe514..4ec9b80a7 100644 --- a/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValue.tsx +++ b/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValue.tsx @@ -1,17 +1,20 @@ import { ANONYMIZED_FIELD_VALUE, - DatasetMetadataFieldValue as DatasetMetadataFieldValueModel + DatasetMetadataFieldValue as DatasetMetadataFieldValueModel, + MetadataBlockName } from '../../../../dataset/domain/models/Dataset' import { useAnonymized } from '../../anonymized/AnonymizedContext' import { useTranslation } from 'react-i18next' import { DatasetMetadataFieldValueFormatted } from './DatasetMetadataFieldValueFormatted' interface DatasetMetadataFieldValueProps { + metadataBlockName: MetadataBlockName metadataFieldName: string metadataFieldValue: DatasetMetadataFieldValueModel } export function DatasetMetadataFieldValue({ + metadataBlockName, metadataFieldName, metadataFieldValue }: DatasetMetadataFieldValueProps) { @@ -23,6 +26,7 @@ export function DatasetMetadataFieldValue({ return ( diff --git a/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValueFormatted.tsx b/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValueFormatted.tsx index c0bc09b14..b3cb7d7a7 100644 --- a/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValueFormatted.tsx +++ b/src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValueFormatted.tsx @@ -1,33 +1,43 @@ import { useMetadataBlockInfo } from '../../metadata-block-info/MetadataBlockInfoContext' import { + METADATA_FIELD_DISPLAY_FORMAT_NAME_PLACEHOLDER, METADATA_FIELD_DISPLAY_FORMAT_PLACEHOLDER, MetadataBlockInfo } from '../../../../metadata-block-info/domain/models/MetadataBlockInfo' import { MarkdownComponent } from '../../markdown/MarkdownComponent' import { DatasetMetadataFieldValue as DatasetMetadataFieldValueModel, - DatasetMetadataSubField + DatasetMetadataSubField, + MetadataBlockName } from '../../../../dataset/domain/models/Dataset' +import { useTranslation } from 'react-i18next' interface DatasetMetadataFieldValueFormattedProps { + metadataBlockName: MetadataBlockName metadataFieldName: string metadataFieldValue: DatasetMetadataFieldValueModel } export function DatasetMetadataFieldValueFormatted({ + metadataBlockName, metadataFieldName, metadataFieldValue }: DatasetMetadataFieldValueFormattedProps) { + const { t } = useTranslation(metadataBlockName) const { metadataBlockInfo } = useMetadataBlockInfo() - const metadataFieldValueMarkdown = metadataFieldValueToMarkdownFormat( + const valueFormatted = metadataFieldValueToDisplayFormat( metadataFieldName, metadataFieldValue, metadataBlockInfo ) + const valueFormattedWithNamesTranslated = valueFormatted.replaceAll( + METADATA_FIELD_DISPLAY_FORMAT_NAME_PLACEHOLDER, + t(`${metadataBlockName}.datasetField.${metadataFieldName}.name`) + ) - return + return } -export function metadataFieldValueToMarkdownFormat( +export function metadataFieldValueToDisplayFormat( metadataFieldName: string, metadataFieldValue: DatasetMetadataFieldValueModel, metadataBlockInfo?: MetadataBlockInfo diff --git a/tests/component/sections/dataset/dataset-metadata/DatasetMetadata.spec.tsx b/tests/component/sections/dataset/dataset-metadata/DatasetMetadata.spec.tsx index f4718182a..de70cdffa 100644 --- a/tests/component/sections/dataset/dataset-metadata/DatasetMetadata.spec.tsx +++ b/tests/component/sections/dataset/dataset-metadata/DatasetMetadata.spec.tsx @@ -7,11 +7,12 @@ import { import { AnonymizedContext } from '../../../../../src/sections/dataset/anonymized/AnonymizedContext' import { isArrayOfObjects, - metadataFieldValueToMarkdownFormat + metadataFieldValueToDisplayFormat } from '../../../../../src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValueFormatted' import { MetadataBlockInfoProvider } from '../../../../../src/sections/dataset/metadata-block-info/MetadataBlockProvider' import { MetadataBlockInfoRepository } from '../../../../../src/metadata-block-info/domain/repositories/MetadataBlockInfoRepository' import { MetadataBlockInfoMother } from '../../../metadata-block-info/domain/models/MetadataBlockInfoMother' +import { METADATA_FIELD_DISPLAY_FORMAT_NAME_PLACEHOLDER } from '../../../../../src/metadata-block-info/domain/models/MetadataBlockInfo' const extractLinksFromText = (text: string): { text: string; link: string }[] => { const linkFormat = /(? { if (notPlainText) { if (extractedLinks) { extractedLinks.forEach(({ text, link }) => { - cy.findByText(text).should('exist') - cy.findByText(text).should('have.attr', 'href', link) + const translatedText = text.replaceAll( + METADATA_FIELD_DISPLAY_FORMAT_NAME_PLACEHOLDER, + metadataFieldName + ) + cy.findByText(translatedText).should('exist') + cy.findByText(translatedText).should('have.attr', 'href', link) }) } if (extractedImages) { extractedImages.forEach((image) => { const [, altText, imageUrl] = image.match(/!\[(.*?)\]\((.*?)\)/) || [] - cy.findByAltText(altText).should('exist') - cy.findByAltText(altText).should('have.attr', 'src', imageUrl) + const translatedAltText = altText.replaceAll( + METADATA_FIELD_DISPLAY_FORMAT_NAME_PLACEHOLDER, + metadataFieldName + ) + cy.findByAltText(translatedAltText).should('exist') + cy.findByAltText(translatedAltText).should('have.attr', 'src', imageUrl) }) } } else { @@ -178,7 +187,9 @@ describe('DatasetMetadata', () => { } Object.entries(metadataBlock.fields).forEach(([metadataFieldName, metadataFieldValue]) => { - const metadataFieldValueString = metadataFieldValueToMarkdownFormat( + const metadataFieldNameTranslated = t[metadataBlock.name].datasetField[metadataFieldName] + .name as string + const metadataFieldValueString = metadataFieldValueToDisplayFormat( metadataFieldName, metadataFieldValue, metadataBlockInfoMock @@ -186,12 +197,12 @@ describe('DatasetMetadata', () => { if (isArrayOfObjects(metadataFieldValue)) { metadataFieldValueString.split(' \n \n').forEach((fieldValue) => { - checkMetadataFieldValue(metadataFieldName, fieldValue) + checkMetadataFieldValue(metadataFieldNameTranslated, fieldValue) }) return } - checkMetadataFieldValue(metadataFieldName, metadataFieldValueString) + checkMetadataFieldValue(metadataFieldNameTranslated, metadataFieldValueString) }) }) }) diff --git a/tests/component/sections/dataset/dataset-summary/SummaryFields.spec.tsx b/tests/component/sections/dataset/dataset-summary/SummaryFields.spec.tsx index 1682e93b2..903d9664e 100644 --- a/tests/component/sections/dataset/dataset-summary/SummaryFields.spec.tsx +++ b/tests/component/sections/dataset/dataset-summary/SummaryFields.spec.tsx @@ -3,7 +3,7 @@ import { SummaryFields } from '../../../../../src/sections/dataset/dataset-summa import { DatasetMother } from '../../../dataset/domain/models/DatasetMother' import { isArrayOfObjects, - metadataFieldValueToMarkdownFormat + metadataFieldValueToDisplayFormat } from '../../../../../src/sections/dataset/dataset-metadata/dataset-metadata-fields/DatasetMetadataFieldValueFormatted' import { MetadataBlockInfoMother } from '../../../metadata-block-info/domain/models/MetadataBlockInfoMother' import { MetadataBlockInfoRepository } from '../../../../../src/metadata-block-info/domain/repositories/MetadataBlockInfoRepository' @@ -38,7 +38,7 @@ describe('DatasetSummary', () => { ) summaryFieldDescription.should('exist') - const summaryFieldValueString = metadataFieldValueToMarkdownFormat( + const summaryFieldValueString = metadataFieldValueToDisplayFormat( summaryFieldName, summaryFieldValue, metadataBlockInfoMock From 86a9b853b6c69f60486495d6862e30fa28d5c42c Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 8 Aug 2023 10:53:11 +0200 Subject: [PATCH 08/21] feat(publicationData,citationDate, alternativePersistentId): implement using js-dataverse --- src/dataset/infrastructure/mappers/JSDatasetMapper.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index f14f4beb3..f63bc52ec 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -24,7 +24,12 @@ export class JSDatasetMapper { citation, JSDatasetMapper.toSummaryFields(jsDataset.metadataBlocks, summaryFieldsNames), jsDataset.license, - JSDatasetMapper.toMetadataBlocks(jsDataset.metadataBlocks) // TODO Add alternativePersistentId, publicationDate, citationDate + JSDatasetMapper.toMetadataBlocks( + jsDataset.metadataBlocks, + jsDataset.alternativePersistentId, + jsDataset.publicationDate, + jsDataset.citationDate + ) ).build() } From 32a8225d214e8f4c6c8fed9cb35088f9e4462946 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 8 Aug 2023 12:13:25 +0200 Subject: [PATCH 09/21] fix(dev-env): restore configbaker timeout --- dev-env/run-env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-env/run-env.sh b/dev-env/run-env.sh index 28ebd20c6..a8a2f09cd 100755 --- a/dev-env/run-env.sh +++ b/dev-env/run-env.sh @@ -6,7 +6,7 @@ export DATAVERSE_IMAGE_TAG=$1 export COMPOSE_HTTP_TIMEOUT=200 # Timeout for Dataverse bootstrap configbaker -export DATAVERSE_BOOTSTRAP_TIMEOUT="10m" +export DATAVERSE_BOOTSTRAP_TIMEOUT="5m" echo "INFO - Setting up Dataverse on image tag ${DATAVERSE_IMAGE_TAG}..." From 9360e2872c2a637518209728d36d224d65bf07ca Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 8 Aug 2023 12:45:07 +0200 Subject: [PATCH 10/21] fix(e2e action): increase wait for containers' timeout --- .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 001a50659..f6a07776f 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 300s 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: Run e2e tests run: npm run test:e2e From c7cbfbe9b9ce8ed1496e1e5e634e92c63da3d0ff Mon Sep 17 00:00:00 2001 From: MellyGray Date: Thu, 10 Aug 2023 10:49:13 +0200 Subject: [PATCH 11/21] fix(typo): replace Filter Type and Filter Tag by File Type and File Tags --- public/locales/en/files.json | 4 +-- .../dataset-files/DatasetFiles.spec.tsx | 6 ++-- .../FileCriteriaFilterByTag.spec.tsx | 16 +++++----- .../FileCriteriaFilterByType.spec.tsx | 16 +++++----- .../FileCriteriaFilters.spec.tsx | 8 ++--- .../FileCriteriaForm.spec.tsx | 32 +++++++++---------- 6 files changed, 41 insertions(+), 41 deletions(-) diff --git a/public/locales/en/files.json b/public/locales/en/files.json index ee3d85536..4814b3f5d 100644 --- a/public/locales/en/files.json +++ b/public/locales/en/files.json @@ -61,10 +61,10 @@ "title": "Filter by" }, "filterByType": { - "title": "Filter Type" + "title": "File Type" }, "filterByTag": { - "title": "Filter Tag" + "title": "File Tags" }, "filterByAccess": { "title": "Access", diff --git a/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx b/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx index 08a5c3e75..701030599 100644 --- a/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx @@ -82,9 +82,9 @@ describe('DatasetFiles', () => { ) cy.findByRole('button', { name: /Sort/ }).should('not.exist') - cy.findByRole('button', { name: 'Filter Type: All' }).should('not.exist') + cy.findByRole('button', { name: 'File Type: All' }).should('not.exist') cy.findByRole('button', { name: 'Access: All' }).should('not.exist') - cy.findByRole('button', { name: 'Filter Tag: All' }).should('not.exist') + cy.findByRole('button', { name: 'File Tags: All' }).should('not.exist') cy.findByText('There are no files in this dataset.').should('exist') }) @@ -122,7 +122,7 @@ describe('DatasetFiles', () => { new FileCriteria().withSortBy(FileSortByOption.NAME_AZ) ) - cy.findByRole('button', { name: 'Filter Type: All' }).should('exist') + cy.findByRole('button', { name: 'File Type: All' }).should('exist') }) it('calls the useFiles hook with the correct parameters when searchText criteria changes', () => { 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 96bbcffcf..d2acad803 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 @@ -30,7 +30,7 @@ describe('FilesCriteriaFilterByTag', () => { /> ) - cy.findByRole('button', { name: 'Filter Tag: All' }).click() + cy.findByRole('button', { name: 'File Tags: All' }).click() cy.findByText('All').should('exist') cy.findByText('Document (5)').should('exist') @@ -48,11 +48,11 @@ describe('FilesCriteriaFilterByTag', () => { /> ) - cy.findByRole('button', { name: 'Filter Tag: All' }).click() + cy.findByRole('button', { name: 'File Tags: All' }).click() cy.findByText('Document (5)').click() cy.wrap(onCriteriaChange).should('be.calledWith', defaultCriteria.withFilterByTag('document')) - cy.findByRole('button', { name: 'Filter Tag: Document' }).click() + cy.findByRole('button', { name: 'File Tags: Document' }).click() cy.findByText('Data (10)').click() cy.wrap(onCriteriaChange).should('be.calledWith', defaultCriteria.withFilterByTag('data')) }) @@ -69,7 +69,7 @@ describe('FilesCriteriaFilterByTag', () => { /> ) - cy.findByRole('button', { name: 'Filter Tag: Document' }).click() + cy.findByRole('button', { name: 'File Tags: Document' }).click() cy.findByText('All').should('exist').click() cy.wrap(onCriteriaChange).should('be.calledWith', defaultCriteria.withFilterByType(undefined)) }) @@ -85,15 +85,15 @@ describe('FilesCriteriaFilterByTag', () => { /> ) - cy.findByRole('button', { name: 'Filter Tag: All' }).click() + cy.findByRole('button', { name: 'File Tags: All' }).click() cy.findByText('All').should('have.class', styles['selected-option']) cy.findByRole('button', { name: 'Document (5)' }).click() - cy.findByRole('button', { name: 'Filter Tag: Document' }).click() + cy.findByRole('button', { name: 'File Tags: Document' }).click() cy.findByText('Document (5)').should('have.class', styles['selected-option']) cy.findByRole('button', { name: 'Data (10)' }).click() - cy.findByRole('button', { name: 'Filter Tag: Data' }).click() + cy.findByRole('button', { name: 'File Tags: Data' }).click() cy.findByText('Data (10)').should('have.class', styles['selected-option']) }) @@ -111,6 +111,6 @@ describe('FilesCriteriaFilterByTag', () => { /> ) - cy.findByRole('button', { name: 'Filter Tag: All' }).should('not.exist') + cy.findByRole('button', { name: 'File Tags: All' }).should('not.exist') }) }) 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 3751936b6..e6c72496f 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 @@ -30,7 +30,7 @@ describe('FilesCriteriaFilterByType', () => { /> ) - cy.findByRole('button', { name: 'Filter Type: All' }).click() + cy.findByRole('button', { name: 'File Type: All' }).click() cy.findByText('All').should('exist') cy.findByText('Image (5)').should('exist') @@ -48,11 +48,11 @@ describe('FilesCriteriaFilterByType', () => { /> ) - cy.findByRole('button', { name: 'Filter Type: All' }).click() + 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: 'Filter Type: Image' }).click() + cy.findByRole('button', { name: 'File Type: Image' }).click() cy.findByText('Text (10)').click() cy.wrap(onCriteriaChange).should('be.calledWith', defaultCriteria.withFilterByType('text')) }) @@ -69,7 +69,7 @@ describe('FilesCriteriaFilterByType', () => { /> ) - cy.findByRole('button', { name: 'Filter Type: Image' }).click() + cy.findByRole('button', { name: 'File Type: Image' }).click() cy.findByText('All').should('exist').click() cy.wrap(onCriteriaChange).should('be.calledWith', defaultCriteria.withFilterByType(undefined)) }) @@ -85,15 +85,15 @@ describe('FilesCriteriaFilterByType', () => { /> ) - cy.findByRole('button', { name: 'Filter Type: All' }).click() + 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: 'Filter Type: Image' }).click() + cy.findByRole('button', { name: 'File Type: Image' }).click() cy.findByText('Image (5)').should('have.class', styles['selected-option']) cy.findByRole('button', { name: 'Text (10)' }).click() - cy.findByRole('button', { name: 'Filter Type: Text' }).click() + cy.findByRole('button', { name: 'File Type: Text' }).click() cy.findByText('Text (10)').should('have.class', styles['selected-option']) }) @@ -109,6 +109,6 @@ describe('FilesCriteriaFilterByType', () => { /> ) - cy.findByRole('button', { name: 'Filter Type: Image' }).should('not.exist') + cy.findByRole('button', { name: 'File Type: Image' }).should('not.exist') }) }) diff --git a/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilters.spec.tsx b/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilters.spec.tsx index 986860a8e..88ec100ed 100644 --- a/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilters.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilters.spec.tsx @@ -31,9 +31,9 @@ describe('FilesCriteriaFilters', () => { cy.findByText('Filter by').should('exist') - cy.findByRole('button', { name: 'Filter Type: All' }).should('exist') + cy.findByRole('button', { name: 'File Type: All' }).should('exist') cy.findByRole('button', { name: 'Access: All' }).should('exist') - cy.findByRole('button', { name: 'Filter Tag: All' }).should('exist') + cy.findByRole('button', { name: 'File Tags: All' }).should('exist') }) it('does not render filters by type options when there are no filters to be applied', () => { @@ -50,8 +50,8 @@ describe('FilesCriteriaFilters', () => { cy.findByText('Filter by').should('not.exist') - cy.findByRole('button', { name: 'Filter Type: All' }).should('not.exist') + cy.findByRole('button', { name: 'File Type: All' }).should('not.exist') cy.findByRole('button', { name: 'Access: All' }).should('not.exist') - cy.findByRole('button', { name: 'Filter Tag: All' }).should('not.exist') + cy.findByRole('button', { name: 'File Tags: All' }).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 c22ec5245..1ad307d6d 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 @@ -55,9 +55,9 @@ describe('FileCriteriaForm', () => { ) cy.findByRole('button', { name: /Sort/ }).should('not.exist') - cy.findByRole('button', { name: 'Filter Type: All' }).should('not.exist') + cy.findByRole('button', { name: 'File Type: All' }).should('not.exist') cy.findByRole('button', { name: 'Access: All' }).should('not.exist') - cy.findByRole('button', { name: 'Filter Tag: All' }).should('not.exist') + cy.findByRole('button', { name: 'File Tags: All' }).should('not.exist') cy.findByLabelText('Search').should('not.exist') }) @@ -82,9 +82,9 @@ describe('FileCriteriaForm', () => { /> ) - cy.findByRole('button', { name: 'Filter Type: All' }).should('exist') + cy.findByRole('button', { name: 'File Type: All' }).should('exist') cy.findByRole('button', { name: 'Access: All' }).should('exist') - cy.findByRole('button', { name: 'Filter Tag: All' }).should('exist') + cy.findByRole('button', { name: 'File Tags: All' }).should('exist') cy.findByText('Filter by').should('exist') }) @@ -117,9 +117,9 @@ describe('FileCriteriaForm', () => { cy.findByRole('button', { name: /Sort/ }).click() cy.findByText('Oldest').click() - cy.findByRole('button', { name: 'Filter Type: Image' }).should('exist') + cy.findByRole('button', { name: 'File Type: Image' }).should('exist') cy.findByRole('button', { name: 'Access: Public' }).should('exist') - cy.findByRole('button', { name: 'Filter Tag: Document' }).should('exist') + cy.findByRole('button', { name: 'File Tags: Document' }).should('exist') }) it('saves global criteria when the filter by type option changes', () => { @@ -136,12 +136,12 @@ describe('FileCriteriaForm', () => { /> ) - cy.findByRole('button', { name: 'Filter Type: Image' }).click() + cy.findByRole('button', { name: 'File Type: Image' }).click() cy.findByText('Text (10)').click() - cy.findByRole('button', { name: 'Filter Type: Text' }).should('exist') + cy.findByRole('button', { name: 'File Type: Text' }).should('exist') cy.findByRole('button', { name: 'Access: Public' }).should('exist') - cy.findByRole('button', { name: 'Filter Tag: Document' }).should('exist') + cy.findByRole('button', { name: 'File Tags: Document' }).should('exist') }) it('saves global criteria when the filter by access option changes', () => { @@ -161,9 +161,9 @@ describe('FileCriteriaForm', () => { cy.findByRole('button', { name: 'Access: Public' }).click() cy.findByText('Restricted (10)').click() - cy.findByRole('button', { name: 'Filter Type: Image' }).should('exist') + cy.findByRole('button', { name: 'File Type: Image' }).should('exist') cy.findByRole('button', { name: 'Access: Restricted' }).should('exist') - cy.findByRole('button', { name: 'Filter Tag: Document' }).should('exist') + cy.findByRole('button', { name: 'File Tags: Document' }).should('exist') }) it('saves global criteria when the filter by tag option changes', () => { @@ -180,12 +180,12 @@ describe('FileCriteriaForm', () => { /> ) - cy.findByRole('button', { name: 'Filter Tag: Document' }).click() + cy.findByRole('button', { name: 'File Tags: Document' }).click() cy.findByText('Data (10)').click() - cy.findByRole('button', { name: 'Filter Type: Image' }).should('exist') + cy.findByRole('button', { name: 'File Type: Image' }).should('exist') cy.findByRole('button', { name: 'Access: Public' }).should('exist') - cy.findByRole('button', { name: 'Filter Tag: Data' }).should('exist') + cy.findByRole('button', { name: 'File Tags: Data' }).should('exist') }) it('saves global criteria when the search input changes', () => { @@ -205,9 +205,9 @@ describe('FileCriteriaForm', () => { cy.findByLabelText('Search').clear().type('new search') - cy.findByRole('button', { name: 'Filter Type: Image' }).should('exist') + cy.findByRole('button', { name: 'File Type: Image' }).should('exist') cy.findByRole('button', { name: 'Access: Public' }).should('exist') - cy.findByRole('button', { name: 'Filter Tag: Document' }).should('exist') + cy.findByRole('button', { name: 'File Tags: Document' }).should('exist') cy.findByLabelText('Search').should('have.value', 'new search') }) }) From 92916f6937e65b6574c9054cfe8b9c9d743ea576 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 17 Aug 2023 14:50:15 +0100 Subject: [PATCH 12/21] Changed: using flat secrets instead of environment secrets in deploy-beta-testing action --- .github/workflows/deploy-beta-testing.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deploy-beta-testing.yml b/.github/workflows/deploy-beta-testing.yml index b7161b6e9..627fae996 100644 --- a/.github/workflows/deploy-beta-testing.yml +++ b/.github/workflows/deploy-beta-testing.yml @@ -8,7 +8,6 @@ on: jobs: build: runs-on: ubuntu-latest - environment: beta-testing steps: - uses: actions/checkout@v3 @@ -33,7 +32,7 @@ jobs: - name: Create and populate .env file env: - DATAVERSE_BACKEND_URL: ${{ secrets.DATAVERSE_BACKEND_URL }} + DATAVERSE_BACKEND_URL: ${{ secrets.BETA_DATAVERSE_BACKEND_URL }} run: | touch .env echo VITE_DATAVERSE_BACKEND_URL="$DATAVERSE_BACKEND_URL" >> .env @@ -50,7 +49,6 @@ jobs: deploy-to-payara: needs: build runs-on: ubuntu-latest - environment: beta-testing steps: - uses: actions/checkout@v3 @@ -76,19 +74,19 @@ jobs: - name: Copy war file to remote instance uses: appleboy/scp-action@master with: - host: ${{ secrets.PAYARA_INSTANCE_HOST }} - username: ${{ secrets.PAYARA_INSTANCE_USERNAME }} - key: ${{ secrets.PAYARA_INSTANCE_SSH_PRIVATE_KEY }} + host: ${{ secrets.BETA_PAYARA_INSTANCE_HOST }} + username: ${{ secrets.BETA_PAYARA_INSTANCE_USERNAME }} + key: ${{ secrets.BETA_PAYARA_INSTANCE_SSH_PRIVATE_KEY }} source: './deployment/payara/target/dataverse-frontend.war' - target: '/home/${{ secrets.PAYARA_INSTANCE_USERNAME }}' + target: '/home/${{ secrets.BETA_PAYARA_INSTANCE_USERNAME }}' overwrite: true - name: Execute payara war deployment remotely uses: appleboy/ssh-action@v0.1.8 with: - host: ${{ secrets.PAYARA_INSTANCE_HOST }} - username: ${{ secrets.PAYARA_INSTANCE_USERNAME }} - key: ${{ secrets.PAYARA_INSTANCE_SSH_PRIVATE_KEY }} + host: ${{ secrets.BETA_PAYARA_INSTANCE_HOST }} + username: ${{ secrets.BETA_PAYARA_INSTANCE_USERNAME }} + key: ${{ secrets.BETA_PAYARA_INSTANCE_SSH_PRIVATE_KEY }} script: | APPLICATION_NAME=dataverse-frontend APPLICATION_WAR_PATH=deployment/payara/target/$APPLICATION_NAME.war From 01c969523de64700f851c45210c7dd23a176902f Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 17 Aug 2023 16:14:54 +0100 Subject: [PATCH 13/21] Changed: switched from payara5 to payara6 in GitHub actions --- .github/workflows/deploy-beta-testing.yml | 2 +- .github/workflows/deploy.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-beta-testing.yml b/.github/workflows/deploy-beta-testing.yml index 627fae996..cad70ab87 100644 --- a/.github/workflows/deploy-beta-testing.yml +++ b/.github/workflows/deploy-beta-testing.yml @@ -90,7 +90,7 @@ jobs: script: | APPLICATION_NAME=dataverse-frontend APPLICATION_WAR_PATH=deployment/payara/target/$APPLICATION_NAME.war - ASADMIN='/usr/local/payara5/bin/asadmin --user admin' + ASADMIN='/usr/local/payara6/bin/asadmin --user admin' DATAVERSE_FRONTEND=`$ASADMIN list-applications |grep $APPLICATION_NAME |awk '{print $1}'` $ASADMIN undeploy $DATAVERSE_FRONTEND $ASADMIN deploy --name $APPLICATION_NAME --contextroot /spa $APPLICATION_WAR_PATH diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 941e7d8ba..df1e495d7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -143,7 +143,7 @@ jobs: script: | APPLICATION_NAME=dataverse-frontend APPLICATION_WAR_PATH=deployment/payara/target/$APPLICATION_NAME.war - ASADMIN='/usr/local/payara5/bin/asadmin --user admin' + ASADMIN='/usr/local/payara6/bin/asadmin --user admin' DATAVERSE_FRONTEND=`$ASADMIN list-applications |grep $APPLICATION_NAME |awk '{print $1}'` $ASADMIN undeploy $DATAVERSE_FRONTEND $ASADMIN deploy --name $APPLICATION_NAME --contextroot /${{ github.event.inputs.basepath }} $APPLICATION_WAR_PATH From f662270f8a0d8bc1379b68f0c1e510549f5795fd Mon Sep 17 00:00:00 2001 From: MellyGray Date: Fri, 1 Sep 2023 11:26:44 +0200 Subject: [PATCH 14/21] fix: cast number to string instead of type assertion --- .../dataset-files/files-table/row-selection/useRowSelection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sections/dataset/dataset-files/files-table/row-selection/useRowSelection.ts b/src/sections/dataset/dataset-files/files-table/row-selection/useRowSelection.ts index 55c0a468e..24e15dc6c 100644 --- a/src/sections/dataset/dataset-files/files-table/row-selection/useRowSelection.ts +++ b/src/sections/dataset/dataset-files/files-table/row-selection/useRowSelection.ts @@ -95,7 +95,7 @@ export function createRowSelection(numberOfRows: number) { const rowSelection: Record = {} for (let i = 0; i < numberOfRows; i++) { - rowSelection[i as unknown as string] = true + rowSelection[String(i)] = true } return rowSelection From d695c0cdf255177ff0434e4d31a119781ae433b6 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Fri, 1 Sep 2023 11:30:02 +0200 Subject: [PATCH 15/21] fix: rename total attribute of FilePaginationInfo for better readability --- src/files/domain/models/FilePaginationInfo.ts | 8 ++++---- .../dataset/dataset-files/files-table/FilesTable.tsx | 2 +- .../files-table/row-selection/useRowSelection.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/files/domain/models/FilePaginationInfo.ts b/src/files/domain/models/FilePaginationInfo.ts index 1d6b2a3d6..5f0e1ddcc 100644 --- a/src/files/domain/models/FilePaginationInfo.ts +++ b/src/files/domain/models/FilePaginationInfo.ts @@ -2,14 +2,14 @@ export class FilePaginationInfo { constructor( public readonly page: number = 1, public readonly pageSize: number = 10, - public readonly total: number = 0 + public readonly totalFiles: number = 0 ) {} withTotal(total: number): FilePaginationInfo { return new FilePaginationInfo(this.page, this.pageSize, total) } goToPage(page: number): FilePaginationInfo { - return new FilePaginationInfo(page, this.pageSize, this.total) + return new FilePaginationInfo(page, this.pageSize, this.totalFiles) } goToPreviousPage(): FilePaginationInfo { @@ -27,11 +27,11 @@ export class FilePaginationInfo { const newPage = Math.ceil((this.page * oldPageSize) / newPageSize) return newPage > 0 ? newPage : 1 } - return new FilePaginationInfo(getNewPage(this.pageSize, pageSize), pageSize, this.total) + return new FilePaginationInfo(getNewPage(this.pageSize, pageSize), pageSize, this.totalFiles) } get totalPages(): number { - return Math.ceil(this.total / this.pageSize) + return Math.ceil(this.totalFiles / this.pageSize) } get hasPreviousPage(): boolean { diff --git a/src/sections/dataset/dataset-files/files-table/FilesTable.tsx b/src/sections/dataset/dataset-files/files-table/FilesTable.tsx index f34bd92ce..129792f8f 100644 --- a/src/sections/dataset/dataset-files/files-table/FilesTable.tsx +++ b/src/sections/dataset/dataset-files/files-table/FilesTable.tsx @@ -28,7 +28,7 @@ export function FilesTable({ files, isLoading, paginationInfo }: FilesTableProps { setCurrentPageRowSelection(createRowSelection(paginationInfo.pageSize)) - setRowSelection(createRowSelection(paginationInfo.total)) + setRowSelection(createRowSelection(paginationInfo.totalFiles)) } const clearRowSelection = () => { setCurrentPageRowSelection({}) @@ -71,7 +71,7 @@ export function useRowSelection( } } const isAllRowsSelected = () => { - return Object.keys(rowSelection).length === paginationInfo.total + return Object.keys(rowSelection).length === paginationInfo.totalFiles } useEffect(() => { From 8aab40e444a65cbc3a6f5cf3021c30f8dba7c763 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Fri, 1 Sep 2023 17:09:58 +0200 Subject: [PATCH 16/21] fix(ZipDownloadLimit): it wasn't being rendered when navigating through pagination --- .../dataset-files/files-table/FilesTable.tsx | 12 +- .../row-selection/RowSelectionMessage.tsx | 8 +- .../row-selection/useFileSelection.ts | 114 ++++++++++++++++++ .../row-selection/useRowSelection.ts | 102 ---------------- .../files-table/useFilesTable.tsx | 30 +++-- .../ZipDownloadLimitMessage.tsx | 17 +-- .../dataset-files/DatasetFiles.spec.tsx | 35 +++++- .../RowSelectionMessage.spec.tsx | 14 +-- .../ZipDownloadLimitMessage.spec.tsx | 47 ++++++-- 9 files changed, 227 insertions(+), 152 deletions(-) create mode 100644 src/sections/dataset/dataset-files/files-table/row-selection/useFileSelection.ts delete mode 100644 src/sections/dataset/dataset-files/files-table/row-selection/useRowSelection.ts diff --git a/src/sections/dataset/dataset-files/files-table/FilesTable.tsx b/src/sections/dataset/dataset-files/files-table/FilesTable.tsx index 129792f8f..908d983ec 100644 --- a/src/sections/dataset/dataset-files/files-table/FilesTable.tsx +++ b/src/sections/dataset/dataset-files/files-table/FilesTable.tsx @@ -15,7 +15,7 @@ interface FilesTableProps { } export function FilesTable({ files, isLoading, paginationInfo }: FilesTableProps) { - const { table, rowSelection, selectAllRows, clearRowSelection } = useFilesTable( + const { table, fileSelection, selectAllFiles, clearFileSelection } = useFilesTable( files, paginationInfo ) @@ -26,14 +26,12 @@ export function FilesTable({ files, isLoading, paginationInfo }: FilesTableProps return ( <> - row.original)} + clearRowSelection={clearFileSelection} /> + diff --git a/src/sections/dataset/dataset-files/files-table/row-selection/RowSelectionMessage.tsx b/src/sections/dataset/dataset-files/files-table/row-selection/RowSelectionMessage.tsx index 26d028ec4..82d0c8791 100644 --- a/src/sections/dataset/dataset-files/files-table/row-selection/RowSelectionMessage.tsx +++ b/src/sections/dataset/dataset-files/files-table/row-selection/RowSelectionMessage.tsx @@ -1,10 +1,10 @@ -import { RowSelection } from './useRowSelection' +import { FileSelection } from './useFileSelection' import { Button } from '@iqss/dataverse-design-system' import { useTranslation } from 'react-i18next' import styles from './RowSelectionMessage.module.scss' interface RowSelectionMessageProps { - rowSelection: RowSelection + fileSelection: FileSelection totalFilesCount: number selectAllRows: () => void clearRowSelection: () => void @@ -14,13 +14,13 @@ const MINIMUM_SELECTED_FILES_TO_SHOW_MESSAGE = 0 const MINIMUM_FILES_TO_SHOW_MESSAGE = 10 export function RowSelectionMessage({ - rowSelection, + fileSelection, totalFilesCount, selectAllRows, clearRowSelection }: RowSelectionMessageProps) { const { t } = useTranslation('files') - const selectedFilesCount = Object.keys(rowSelection).length + const selectedFilesCount = Object.keys(fileSelection).length const showMessage = totalFilesCount > MINIMUM_FILES_TO_SHOW_MESSAGE && selectedFilesCount > MINIMUM_SELECTED_FILES_TO_SHOW_MESSAGE diff --git a/src/sections/dataset/dataset-files/files-table/row-selection/useFileSelection.ts b/src/sections/dataset/dataset-files/files-table/row-selection/useFileSelection.ts new file mode 100644 index 000000000..e8e9fec2c --- /dev/null +++ b/src/sections/dataset/dataset-files/files-table/row-selection/useFileSelection.ts @@ -0,0 +1,114 @@ +import { useEffect, useState } from 'react' +import { FilePaginationInfo } from '../../../../../files/domain/models/FilePaginationInfo' +import { File } from '../../../../../files/domain/models/File' +import { Row } from '@tanstack/react-table' +import { RowSelection } from '../useFilesTable' + +export type FileSelection = { + [key: string]: File | undefined +} + +export function useFileSelection( + currentPageSelectedRowModel: Record>, + setCurrentPageRowSelection: (rowSelection: RowSelection) => void, + paginationInfo: FilePaginationInfo +) { + const [fileSelection, setFileSelection] = useState({}) + const updateFileSelection = () => { + const currentPageFileSelection = getCurrentPageFileSelection() + const currentPageIndexes = getCurrentPageIndexes() + + Object.keys(fileSelection).forEach((key) => { + const rowIndex = parseInt(key) + if (currentPageIndexes.includes(rowIndex)) { + if (!currentPageFileSelection[key]) { + delete fileSelection[key] + } + } + }) + + return { ...fileSelection, ...currentPageFileSelection } + } + const getCurrentPageIndexes = () => { + return Array.from( + { length: paginationInfo.pageSize }, + (_, i) => i + (paginationInfo.page - 1) * paginationInfo.pageSize + ) + } + const getCurrentPageFileSelection = () => { + const rowSelectionFixed: FileSelection = {} + const currentPageIndexes = getCurrentPageIndexes() + + Object.entries(currentPageSelectedRowModel).forEach(([string, Row]) => { + const rowIndex = parseInt(string) + rowSelectionFixed[currentPageIndexes[rowIndex]] = Row.original + }) + return rowSelectionFixed + } + const computeCurrentPageRowSelection = () => { + const rowSelectionOfCurrentPage: RowSelection = {} + const currentPageIndexes = getCurrentPageIndexes() + + Object.keys(fileSelection).forEach((key) => { + const rowIndex = parseInt(key) + if (currentPageIndexes.includes(rowIndex)) { + rowSelectionOfCurrentPage[currentPageIndexes.indexOf(rowIndex)] = true + } + }) + + return rowSelectionOfCurrentPage + } + const selectAllFiles = () => { + setCurrentPageRowSelection(createRowSelection(paginationInfo.pageSize)) + setFileSelection(createFileSelection(paginationInfo.totalFiles)) + } + const clearFileSelection = () => { + setCurrentPageRowSelection({}) + setFileSelection({}) + } + const toggleAllFilesSelected = () => { + if (areAllFilesSelected()) { + clearFileSelection() + } else { + selectAllFiles() + } + } + const areAllFilesSelected = () => { + return Object.keys(fileSelection).length === paginationInfo.totalFiles + } + + useEffect(() => { + setFileSelection(updateFileSelection()) + }, [currentPageSelectedRowModel]) + + useEffect(() => { + setCurrentPageRowSelection(computeCurrentPageRowSelection()) + }, [paginationInfo]) + + return { + fileSelection, + selectAllFiles, + clearFileSelection, + toggleAllFilesSelected + } +} + +export function createRowSelection(numberOfRows: number) { + const rowSelection: Record = {} + + for (let i = 0; i < numberOfRows; i++) { + rowSelection[String(i)] = true + } + + return rowSelection +} + +export function createFileSelection(numberOfRows: number) { + const fileSelection: FileSelection = {} + + for (let i = 0; i < numberOfRows; i++) { + fileSelection[String(i)] = undefined + } + + return fileSelection +} diff --git a/src/sections/dataset/dataset-files/files-table/row-selection/useRowSelection.ts b/src/sections/dataset/dataset-files/files-table/row-selection/useRowSelection.ts deleted file mode 100644 index d7aa68b96..000000000 --- a/src/sections/dataset/dataset-files/files-table/row-selection/useRowSelection.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { useEffect, useState } from 'react' -import { FilePaginationInfo } from '../../../../../files/domain/models/FilePaginationInfo' - -export type RowSelection = { - [key: string]: boolean -} - -export function useRowSelection( - currentPageRowSelection: RowSelection, - setCurrentPageRowSelection: (rowSelection: RowSelection) => void, - paginationInfo: FilePaginationInfo -) { - const [rowSelection, setRowSelection] = useState({}) - const updatedRowSelection = () => { - const currentPageRowSelectionFixed = getCurrentPageRowSelectionFixed() - const currentPageIndexes = getCurrentPageIndexes() - - Object.keys(rowSelection).forEach((key) => { - const rowIndex = parseInt(key) - if (currentPageIndexes.includes(rowIndex)) { - if (!currentPageRowSelectionFixed[key]) { - delete rowSelection[key] - } - } - }) - - return { ...rowSelection, ...currentPageRowSelectionFixed } - } - const getCurrentPageIndexes = () => { - return Array.from( - { length: paginationInfo.pageSize }, - (_, i) => i + (paginationInfo.page - 1) * paginationInfo.pageSize - ) - } - const getCurrentPageRowSelectionFixed = () => { - const rowSelectionFixed: RowSelection = {} - const currentPageIndexes = getCurrentPageIndexes() - - Object.keys(currentPageRowSelection).forEach((key) => { - const rowIndex = parseInt(key) - rowSelectionFixed[currentPageIndexes[rowIndex]] = currentPageRowSelection[key] - }) - return rowSelectionFixed - } - const getRowSelectionOfCurrentPage = () => { - const rowSelectionOfCurrentPage: RowSelection = {} - const currentPageIndexes = getCurrentPageIndexes() - - Object.keys(rowSelection).forEach((key) => { - const rowIndex = parseInt(key) - if (currentPageIndexes.includes(rowIndex)) { - rowSelectionOfCurrentPage[currentPageIndexes.indexOf(rowIndex)] = rowSelection[key] - } - }) - - return rowSelectionOfCurrentPage - } - const selectAllRows = () => { - setCurrentPageRowSelection(createRowSelection(paginationInfo.pageSize)) - setRowSelection(createRowSelection(paginationInfo.totalFiles)) - } - const clearRowSelection = () => { - setCurrentPageRowSelection({}) - setRowSelection({}) - } - const toggleAllRowsSelected = () => { - if (isAllRowsSelected()) { - clearRowSelection() - } else { - selectAllRows() - } - } - const isAllRowsSelected = () => { - return Object.keys(rowSelection).length === paginationInfo.totalFiles - } - - useEffect(() => { - setRowSelection(updatedRowSelection()) - }, [currentPageRowSelection]) - - useEffect(() => { - setCurrentPageRowSelection(getRowSelectionOfCurrentPage()) - }, [paginationInfo]) - - return { - rowSelection, - setRowSelection, - selectAllRows, - clearRowSelection, - toggleAllRowsSelected - } -} - -export function createRowSelection(numberOfRows: number) { - const rowSelection: Record = {} - - for (let i = 0; i < numberOfRows; i++) { - rowSelection[String(i)] = true - } - - return rowSelection -} diff --git a/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx b/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx index bb524d4b4..467f76e1b 100644 --- a/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx +++ b/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx @@ -1,20 +1,24 @@ import { useEffect, useState } from 'react' import { File } from '../../../../files/domain/models/File' -import { getCoreRowModel, useReactTable } from '@tanstack/react-table' +import { getCoreRowModel, Row, useReactTable } from '@tanstack/react-table' import { createColumnsDefinition } from './FilesTableColumnsDefinition' import { FilePaginationInfo } from '../../../../files/domain/models/FilePaginationInfo' -import { RowSelection, useRowSelection } from './row-selection/useRowSelection' +import { useFileSelection } from './row-selection/useFileSelection' + +export type RowSelection = { + [key: string]: boolean +} export function useFilesTable(files: File[], paginationInfo: FilePaginationInfo) { const [currentPageRowSelection, setCurrentPageRowSelection] = useState({}) - const { rowSelection, selectAllRows, clearRowSelection, toggleAllRowsSelected } = useRowSelection( - currentPageRowSelection, - setCurrentPageRowSelection, - paginationInfo - ) + const [currentPageSelectedRowModel, setCurrentPageSelectedRowModel] = useState< + Record> + >({}) + const { fileSelection, selectAllFiles, clearFileSelection, toggleAllFilesSelected } = + useFileSelection(currentPageSelectedRowModel, setCurrentPageRowSelection, paginationInfo) const table = useReactTable({ data: files, - columns: createColumnsDefinition(toggleAllRowsSelected), + columns: createColumnsDefinition(toggleAllFilesSelected), state: { rowSelection: currentPageRowSelection }, @@ -30,10 +34,14 @@ export function useFilesTable(files: File[], paginationInfo: FilePaginationInfo) table.setPageIndex(paginationInfo.page - 1) }, [paginationInfo]) + useEffect(() => { + setCurrentPageSelectedRowModel(table.getSelectedRowModel().rowsById) + }, [table.getSelectedRowModel().rowsById]) + return { table, - rowSelection, - selectAllRows, - clearRowSelection + fileSelection, + selectAllFiles, + clearFileSelection } } diff --git a/src/sections/dataset/dataset-files/files-table/zip-download-limit-message/ZipDownloadLimitMessage.tsx b/src/sections/dataset/dataset-files/files-table/zip-download-limit-message/ZipDownloadLimitMessage.tsx index a066dba7a..7e1c474ee 100644 --- a/src/sections/dataset/dataset-files/files-table/zip-download-limit-message/ZipDownloadLimitMessage.tsx +++ b/src/sections/dataset/dataset-files/files-table/zip-download-limit-message/ZipDownloadLimitMessage.tsx @@ -5,19 +5,18 @@ import { useSettings } from '../../../../settings/SettingsContext' import { SettingName } from '../../../../../settings/domain/models/Setting' import { ZipDownloadLimit } from '../../../../../settings/domain/models/ZipDownloadLimit' import { useEffect, useState } from 'react' +import { FileSelection } from '../row-selection/useFileSelection' interface ZipDownloadLimitMessageProps { - selectedFiles: File[] + fileSelection: FileSelection } const MINIMUM_FILES_TO_SHOW_MESSAGE = 1 -export function ZipDownloadLimitMessage({ selectedFiles }: ZipDownloadLimitMessageProps) { +export function ZipDownloadLimitMessage({ fileSelection }: ZipDownloadLimitMessageProps) { const { t } = useTranslation('files') const { getSettingByName } = useSettings() const [zipDownloadLimitInBytes, setZipDownloadLimitInBytes] = useState() - const selectionTotalSizeInBytes = getFilesTotalSizeInBytes(selectedFiles) - useEffect(() => { getSettingByName(SettingName.ZIP_DOWNLOAD_LIMIT) .then((zipDownloadLimit) => { @@ -28,9 +27,11 @@ export function ZipDownloadLimitMessage({ selectedFiles }: ZipDownloadLimitMessa }) }, [getSettingByName]) + // TODO - When selecting all files, the size should come from a call to a use case that returns the total size of the dataset files. Check issue https://github.com/IQSS/dataverse-frontend/issues/170 + const selectionTotalSizeInBytes = getFilesTotalSizeInBytes(Object.values(fileSelection)) const showMessage = zipDownloadLimitInBytes && - selectedFiles.length > MINIMUM_FILES_TO_SHOW_MESSAGE && + Object.values(fileSelection).length > MINIMUM_FILES_TO_SHOW_MESSAGE && selectionTotalSizeInBytes > zipDownloadLimitInBytes if (!showMessage) { @@ -48,8 +49,10 @@ export function ZipDownloadLimitMessage({ selectedFiles }: ZipDownloadLimitMessa ) } -function getFilesTotalSizeInBytes(files: File[]) { - return files.map((file) => file.size).reduce((bytes, size) => bytes + size.toBytes(), 0) +function getFilesTotalSizeInBytes(files: (File | undefined)[]) { + return files + .map((file) => file?.size) + .reduce((bytes, size) => bytes + (size ? size.toBytes() : 0), 0) } function bytesToHumanReadable(bytes: number) { diff --git a/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx b/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx index 25088e757..2049c0c7d 100644 --- a/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx @@ -9,8 +9,11 @@ import { } from '../../../../../src/files/domain/models/FileCriteria' import { FilesCountInfoMother } from '../../../files/domain/models/FilesCountInfoMother' import { FilePaginationInfo } from '../../../../../src/files/domain/models/FilePaginationInfo' -import { FileType } from '../../../../../src/files/domain/models/File' +import { FileSizeUnit, FileType } from '../../../../../src/files/domain/models/File' import styles from '../../../../../src/sections/dataset/dataset-files/files-table/FilesTable.module.scss' +import { SettingMother } from '../../../settings/domain/models/SettingMother' +import { ZipDownloadLimit } from '../../../../../src/settings/domain/models/ZipDownloadLimit' +import { SettingsContext } from '../../../../../src/sections/settings/SettingsContext' const testFiles = FileMother.createMany(10) const datasetPersistentId = 'test-dataset-persistent-id' @@ -161,6 +164,36 @@ describe('DatasetFiles', () => { cy.findByText('1 file is currently selected.').should('exist') }) + + it('renders the zip download limit message when selecting rows from different pages', () => { + const getSettingByName = cy + .stub() + .resolves(SettingMother.createZipDownloadLimit(new ZipDownloadLimit(1, FileSizeUnit.BYTES))) + + cy.customMount( + + + + ) + + cy.get('table > tbody > tr:nth-child(2) > td:nth-child(1) > input[type=checkbox]').click() + cy.findByRole('button', { name: 'Next' }).click() + cy.get('table > tbody > tr:nth-child(3) > td:nth-child(1) > input[type=checkbox]').click() + + cy.findByText( + /exceeds the zip limit of 1.0 B. Please unselect some files to continue./ + ).should('exist') + + cy.get('table > tbody > tr:nth-child(3) > td:nth-child(1) > input[type=checkbox]').click() + + cy.findByText( + /exceeds the zip limit of 1.0 B. Please unselect some files to continue./ + ).should('not.exist') + }) }) describe('Calling use cases', () => { diff --git a/tests/component/sections/dataset/dataset-files/files-table/row-selection/RowSelectionMessage.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/row-selection/RowSelectionMessage.spec.tsx index e5eb4b997..5584bdb76 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/row-selection/RowSelectionMessage.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/row-selection/RowSelectionMessage.spec.tsx @@ -1,5 +1,5 @@ import { RowSelectionMessage } from '../../../../../../../src/sections/dataset/dataset-files/files-table/row-selection/RowSelectionMessage' -import { createRowSelection } from '../../../../../../../src/sections/dataset/dataset-files/files-table/row-selection/useRowSelection' +import { createRowSelection } from '../../../../../../../src/sections/dataset/dataset-files/files-table/row-selection/useFileSelection' let selectAllRows = () => {} let clearRowSelection = () => {} @@ -12,7 +12,7 @@ describe('RowSelectionMessage', () => { it('renders the message when there are more than 10 files and some row is selected', () => { cy.customMount( { it('does not render the message when there are less than 10 files', () => { cy.customMount( { it('does not render the message when there are more than 10 files but no row is selected', () => { cy.customMount( { it('renders the plural form of the message when there is more than 1 row selected', () => { cy.customMount( { it("calls selectAllRows when the 'Select all' button is clicked", () => { cy.customMount( { it("calls clearRowSelection when the 'Clear selection.' button is clicked", () => { cy.customMount( { - it('should render the component with no message if conditions are not met', () => { + it('should not render if there is less than 1 file selected', () => { const getSettingByName = cy .stub() .resolves(SettingMother.createZipDownloadLimit(zipDownloadLimit)) @@ -19,7 +19,9 @@ describe('ZipDownloadLimitMessage', () => { cy.customMount( ) @@ -27,13 +29,32 @@ describe('ZipDownloadLimitMessage', () => { cy.findByText(/The overall size of the files selected/).should('not.exist') }) - it('should render the component with the appropriate message if conditions are met', () => { + it('should not render if the zipDownloadLimit is not exceeded', () => { + const getSettingByName = cy + .stub() + .resolves(SettingMother.createZipDownloadLimit(zipDownloadLimit)) + + cy.customMount( + + + + ) + + cy.findByText(/The overall size of the files selected/).should('not.exist') + }) + + it('should render if there is more than 1 file and they exceed the zipDownloadLimit', () => { const getSettingByName = cy .stub() .resolves(SettingMother.createZipDownloadLimit(zipDownloadLimit)) cy.customMount( - + ) @@ -46,13 +67,13 @@ describe('ZipDownloadLimitMessage', () => { const getSettingByName = cy .stub() .resolves(SettingMother.createZipDownloadLimit(zipDownloadLimit)) - const files = [ - FileMother.create({ size: new FileSize(1000000, FileSizeUnit.PETABYTES) }), - FileMother.create({ size: new FileSize(1000000, FileSizeUnit.PETABYTES) }) - ] + const fileSelection = { + '1': FileMother.create({ size: new FileSize(1000000, FileSizeUnit.PETABYTES) }), + '2': FileMother.create({ size: new FileSize(1000000, FileSizeUnit.PETABYTES) }) + } cy.customMount( - + ) From aa8f39e532cf2cf17cea490b2fb778e74f97ee76 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Mon, 4 Sep 2023 10:37:42 +0200 Subject: [PATCH 17/21] fix(tests): add wait for FilesTable initialization --- .../dataset/dataset-files/files-table/FilesTable.spec.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/component/sections/dataset/dataset-files/files-table/FilesTable.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/FilesTable.spec.tsx index 4a0eabdfe..3da28781b 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/FilesTable.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/FilesTable.spec.tsx @@ -44,6 +44,8 @@ describe('FilesTable', () => { ) + cy.wait(1000) // wait for the table to load + cy.get('table > thead > tr > th > input[type=checkbox]').click() cy.findByText('200 files are currently selected.').should('exist') @@ -56,6 +58,8 @@ describe('FilesTable', () => { ) + cy.wait(1000) // wait for the table to load + cy.get('table > thead > tr > th > input[type=checkbox]').click() cy.findByText('200 files are currently selected.').should('exist') From e93b8e98e8cc504be985012d7143d56a98d85a08 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 5 Sep 2023 12:09:16 +0200 Subject: [PATCH 18/21] fix: citationDate undefined crashes the Dataset page --- 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 f63bc52ec..53a3a627a 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -148,7 +148,7 @@ export class JSDatasetMapper { extraFields.publicationDate = publicationDate } - if (publicationDate && citationDate !== publicationDate) { + if (citationDate && citationDate !== publicationDate) { extraFields.citationDate = citationDate } From e0a41b7f5875e4087de79afe32899a04bdca78e2 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 5 Sep 2023 16:49:54 +0200 Subject: [PATCH 19/21] fix: add tests for dataset summary --- .../integration/DataverseApiHelper.ts | 9 ++++- .../integration/datasets/DatasetHelper.ts | 12 ++++++ .../DatasetJSDataverseRepository.spec.ts | 37 ++++++++++++++++++- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/tests/e2e-integration/integration/DataverseApiHelper.ts b/tests/e2e-integration/integration/DataverseApiHelper.ts index 742069b78..745729f85 100644 --- a/tests/e2e-integration/integration/DataverseApiHelper.ts +++ b/tests/e2e-integration/integration/DataverseApiHelper.ts @@ -14,13 +14,18 @@ export class DataverseApiHelper { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - static async request(url: string, method: string, data?: any): Promise { + static async request( + url: string, + method: string, + data?: any, + contentType?: string + ): Promise { const config: AxiosRequestConfig = { url: `${this.API_URL}${url}`, method: method, headers: { 'X-Dataverse-key': this.API_TOKEN, - 'Content-Type': 'application/json' + 'Content-Type': contentType ? contentType : 'application/json' }, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment data: data diff --git a/tests/e2e-integration/integration/datasets/DatasetHelper.ts b/tests/e2e-integration/integration/datasets/DatasetHelper.ts index 270d57fae..e1699112d 100644 --- a/tests/e2e-integration/integration/datasets/DatasetHelper.ts +++ b/tests/e2e-integration/integration/datasets/DatasetHelper.ts @@ -21,4 +21,16 @@ export class DatasetHelper extends DataverseApiHelper { static async createPrivateUrl(id: string): Promise<{ token: string }> { return this.request<{ token: string }>(`/datasets/${id}/privateUrl`, 'POST') } + + static async setCitationDateFieldType( + persistentId: string, + fieldType: string + ): Promise<{ status: string }> { + return this.request<{ status: string }>( + `/datasets/:persistentId/citationdate?persistentId=${persistentId}`, + 'PUT', + fieldType, + 'text/plain' + ) + } } diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index 0d9dd37ba..2ae873d48 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -8,6 +8,13 @@ import { DatasetStatus, DatasetVersion } from '../../../../src/dataset/domain/mo chai.use(chaiAsPromised) const expect = chai.expect +function getCurrentDateInYYYYMMDDFormat() { + const date = new Date() + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String( + date.getDate() + ).padStart(2, '0')}` +} + const datasetData = (persistentId: string) => { const persistentIdUrl = `https://doi.org/${persistentId.replace('doi:', '')}` return { @@ -86,6 +93,8 @@ describe('Dataset JSDataverse Repository', () => { expect(dataset.metadataBlocks).to.deep.equal(datasetExpected.metadataBlocks) expect(dataset.summaryFields).to.deep.equal(datasetExpected.summaryFields) 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 }) }) @@ -103,9 +112,13 @@ describe('Dataset JSDataverse Repository', () => { } const datasetExpected = datasetData(dataset.persistentId) const newVersion = new DatasetVersion(1, 0, DatasetStatus.RELEASED) - + 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 }) }) @@ -139,4 +152,26 @@ describe('Dataset JSDataverse Repository', () => { expect(dataset.version).to.deep.equal(datasetExpected.version) }) }) + + it('gets the dataset after changing the citation date field type', async () => { + const datasetResponse = await DatasetHelper.createDataset() + + await DatasetHelper.publishDataset(datasetResponse.persistentId) + await IntegrationTestsUtils.wait(1500) + + await DatasetHelper.setCitationDateFieldType(datasetResponse.persistentId, 'dateOfDeposit') + + await datasetRepository + .getByPersistentId(datasetResponse.persistentId, '1.0') + .then((dataset) => { + if (!dataset) { + throw new Error('Dataset not found') + } + const expectedPublicationDate = getCurrentDateInYYYYMMDDFormat() + expect(dataset.metadataBlocks[0].fields.publicationDate).to.deep.equal( + expectedPublicationDate + ) + expect(dataset.metadataBlocks[0].fields.citationDate).not.to.exist + }) + }) }) From ded688c65faa6c41d3ecaecb1ca86122dab7e3d9 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Thu, 7 Sep 2023 16:03:38 +0200 Subject: [PATCH 20/21] fix: add JSDatasetMapper unit tests --- .../mappers/JSDatasetMapper.spec.ts | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts diff --git a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts new file mode 100644 index 000000000..4ad8930f1 --- /dev/null +++ b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts @@ -0,0 +1,214 @@ +import chai from 'chai' +import chaiAsPromised from 'chai-as-promised' +import { JSDatasetMapper } from '../../../../../src/dataset/infrastructure/mappers/JSDatasetMapper' +import { DatasetVersionState } from '@iqss/dataverse-client-javascript' +import { + CitationMetadataBlock, + DatasetMetadataBlock +} from '@iqss/dataverse-client-javascript/dist/datasets/domain/models/Dataset' +import { DatasetStatus, DatasetVersion } from '../../../../../src/dataset/domain/models/Dataset' + +chai.use(chaiAsPromised) +const expect = chai.expect + +const jsDataset = { + id: 505, + persistentId: 'doi:10.5072/FK2/B4B2MJ', + versionId: 101, + versionInfo: { + state: DatasetVersionState.DRAFT, + majorNumber: 0, + minorNumber: 0, + createTime: new Date('2023-09-07T13:40:04.000Z'), + lastUpdateTime: new Date('2023-09-07T13:40:04.000Z'), + releaseTime: undefined + }, + metadataBlocks: [ + { + name: 'citation', + fields: { + title: "Darwin's Finches", + author: [{ authorName: 'Finch, Fiona', authorAffiliation: 'Birds Inc.' }], + datasetContact: [ + { datasetContactName: 'Finch, Fiona', datasetContactEmail: 'finch@mailinator.com' } + ], + dsDescription: [ + { + dsDescriptionValue: + "Darwin's finches (also known as the Galápagos finches) are a group of about fifteen species of passerine birds." + } + ], + subject: ['Medicine, Health and Life Sciences'] + } + } as CitationMetadataBlock + ] as [CitationMetadataBlock, ...DatasetMetadataBlock[]], + license: { + name: 'CC0 1.0', + uri: 'http://creativecommons.org/publicdomain/zero/1.0', + iconUri: 'https://licensebuttons.net/p/zero/1.0/88x31.png' + } +} +const citation = + 'Finch, Fiona, 2023, "Darwin\'s Finches", https://doi.org/10.5072/FK2/B4B2MJ, Root, DRAFT VERSION' +const datasetSummaryFields = ['dsDescription', 'subject', 'keyword', 'publication', 'notesText'] +const expectedDataset = { + persistentId: 'doi:10.5072/FK2/B4B2MJ', + version: new DatasetVersion(0, 0, DatasetStatus.DRAFT), + citation: + 'Finch, Fiona, 2023, "Darwin\'s Finches", https://doi.org/10.5072/FK2/B4B2MJ, Root, DRAFT VERSION', + labels: [ + { semanticMeaning: 'dataset', value: 'Draft' }, + { semanticMeaning: 'warning', value: 'Unpublished' } + ], + summaryFields: [ + { + name: 'citation', + fields: { + dsDescription: [ + { + dsDescriptionValue: + "Darwin's finches (also known as the Galápagos finches) are a group of about fifteen species of passerine birds." + } + ], + subject: ['Medicine, Health and Life Sciences'] + } + } + ], + license: { + name: 'CC0 1.0', + uri: 'http://creativecommons.org/publicdomain/zero/1.0', + iconUri: 'https://licensebuttons.net/p/zero/1.0/88x31.png' + }, + metadataBlocks: [ + { + name: 'citation', + fields: { + title: "Darwin's Finches", + author: [{ authorName: 'Finch, Fiona', authorAffiliation: 'Birds Inc.' }], + datasetContact: [ + { datasetContactName: 'Finch, Fiona', datasetContactEmail: 'finch@mailinator.com' } + ], + dsDescription: [ + { + dsDescriptionValue: + "Darwin's finches (also known as the Galápagos finches) are a group of about fifteen species of passerine birds." + } + ], + subject: ['Medicine, Health and Life Sciences'] + } + } + ] +} + +describe('JS Dataset Mapper', () => { + it('maps jsDataset model to the domain Dataset model', () => { + expect(expectedDataset).to.deep.equal( + JSDatasetMapper.toDataset(jsDataset, citation, datasetSummaryFields) + ) + }) + + it('maps jsDataset model to the domain Dataset model when alternativePersistentId is provided', () => { + const jsDatasetWithAlternativePersistentId = { + ...jsDataset, + alternativePersistentId: 'doi:10.5072/FK2/B4B2MY' + } + const expectedDatasetWithAlternativePersistentId = { + ...expectedDataset, + metadataBlocks: [ + { + name: 'citation', + fields: { + title: "Darwin's Finches", + author: [{ authorName: 'Finch, Fiona', authorAffiliation: 'Birds Inc.' }], + datasetContact: [ + { datasetContactName: 'Finch, Fiona', datasetContactEmail: 'finch@mailinator.com' } + ], + dsDescription: [ + { + dsDescriptionValue: + "Darwin's finches (also known as the Galápagos finches) are a group of about fifteen species of passerine birds." + } + ], + subject: ['Medicine, Health and Life Sciences'], + alternativePersistentId: 'doi:10.5072/FK2/B4B2MY' + } + } + ] + } + + expect(expectedDatasetWithAlternativePersistentId).to.deep.equal( + JSDatasetMapper.toDataset( + jsDatasetWithAlternativePersistentId, + citation, + datasetSummaryFields + ) + ) + }) + + it('maps jsDataset model to the domain Dataset model when citationDate is provided', () => { + const jsDatasetWithCitationDate = { + ...jsDataset, + citationDate: '2023-02-12' + } + const expectedDatasetWithCitationDate = { + ...expectedDataset, + metadataBlocks: [ + { + name: 'citation', + fields: { + title: "Darwin's Finches", + author: [{ authorName: 'Finch, Fiona', authorAffiliation: 'Birds Inc.' }], + datasetContact: [ + { datasetContactName: 'Finch, Fiona', datasetContactEmail: 'finch@mailinator.com' } + ], + dsDescription: [ + { + dsDescriptionValue: + "Darwin's finches (also known as the Galápagos finches) are a group of about fifteen species of passerine birds." + } + ], + subject: ['Medicine, Health and Life Sciences'], + citationDate: '2023-02-12' + } + } + ] + } + + expect(expectedDatasetWithCitationDate).to.deep.equal( + JSDatasetMapper.toDataset(jsDatasetWithCitationDate, citation, datasetSummaryFields) + ) + }) + + it('maps jsDataset model to the domain Dataset model when publicationDate is provided', () => { + const jsDatasetWithPublicationDate = { + ...jsDataset, + publicationDate: '2023-02-12' + } + const expectedDatasetWithPublicationDate = { + ...expectedDataset, + metadataBlocks: [ + { + name: 'citation', + fields: { + title: "Darwin's Finches", + author: [{ authorName: 'Finch, Fiona', authorAffiliation: 'Birds Inc.' }], + datasetContact: [ + { datasetContactName: 'Finch, Fiona', datasetContactEmail: 'finch@mailinator.com' } + ], + dsDescription: [ + { + dsDescriptionValue: + "Darwin's finches (also known as the Galápagos finches) are a group of about fifteen species of passerine birds." + } + ], + subject: ['Medicine, Health and Life Sciences'], + publicationDate: '2023-02-12' + } + } + ] + } + expect(expectedDatasetWithPublicationDate).to.deep.equal( + JSDatasetMapper.toDataset(jsDatasetWithPublicationDate, citation, datasetSummaryFields) + ) + }) +}) From f0265454da215991da9fb705ef473ecc412747f2 Mon Sep 17 00:00:00 2001 From: MellyGray Date: Tue, 12 Sep 2023 18:16:43 +0200 Subject: [PATCH 21/21] fix(FilesTableRowSelection): global checkbox should select the current page only --- .../files-table/FilesTableColumnsDefinition.tsx | 6 ++---- .../files-table/row-selection/useFileSelection.ts | 13 +------------ .../dataset-files/files-table/useFilesTable.tsx | 11 +++++++---- .../dataset-files/files-table/FilesTable.spec.tsx | 14 ++++++++------ 4 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/sections/dataset/dataset-files/files-table/FilesTableColumnsDefinition.tsx b/src/sections/dataset/dataset-files/files-table/FilesTableColumnsDefinition.tsx index b7d0b4217..f5d6d28bf 100644 --- a/src/sections/dataset/dataset-files/files-table/FilesTableColumnsDefinition.tsx +++ b/src/sections/dataset/dataset-files/files-table/FilesTableColumnsDefinition.tsx @@ -4,9 +4,7 @@ import { RowSelectionCheckbox } from './row-selection/RowSelectionCheckbox' import { FileInfoCell } from './file-info-cell/FileInfoCell' import { FileInfoHeader } from './FileInfoHeader' -export const createColumnsDefinition = ( - toggleAllRowsSelected: (event: unknown) => void -): ColumnDef[] => [ +export const columns: ColumnDef[] = [ { id: 'select', header: ({ table }) => ( @@ -14,7 +12,7 @@ export const createColumnsDefinition = ( {...{ checked: table.getIsAllRowsSelected(), indeterminate: table.getIsSomeRowsSelected(), - onChange: toggleAllRowsSelected, + onChange: table.getToggleAllRowsSelectedHandler(), disabled: table.getPageCount() === 0 }} /> diff --git a/src/sections/dataset/dataset-files/files-table/row-selection/useFileSelection.ts b/src/sections/dataset/dataset-files/files-table/row-selection/useFileSelection.ts index e8e9fec2c..61bc82b40 100644 --- a/src/sections/dataset/dataset-files/files-table/row-selection/useFileSelection.ts +++ b/src/sections/dataset/dataset-files/files-table/row-selection/useFileSelection.ts @@ -66,16 +66,6 @@ export function useFileSelection( setCurrentPageRowSelection({}) setFileSelection({}) } - const toggleAllFilesSelected = () => { - if (areAllFilesSelected()) { - clearFileSelection() - } else { - selectAllFiles() - } - } - const areAllFilesSelected = () => { - return Object.keys(fileSelection).length === paginationInfo.totalFiles - } useEffect(() => { setFileSelection(updateFileSelection()) @@ -88,8 +78,7 @@ export function useFileSelection( return { fileSelection, selectAllFiles, - clearFileSelection, - toggleAllFilesSelected + clearFileSelection } } diff --git a/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx b/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx index 467f76e1b..52cd528fb 100644 --- a/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx +++ b/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import { File } from '../../../../files/domain/models/File' import { getCoreRowModel, Row, useReactTable } from '@tanstack/react-table' -import { createColumnsDefinition } from './FilesTableColumnsDefinition' +import { columns } from './FilesTableColumnsDefinition' import { FilePaginationInfo } from '../../../../files/domain/models/FilePaginationInfo' import { useFileSelection } from './row-selection/useFileSelection' @@ -14,11 +14,14 @@ export function useFilesTable(files: File[], paginationInfo: FilePaginationInfo) const [currentPageSelectedRowModel, setCurrentPageSelectedRowModel] = useState< Record> >({}) - const { fileSelection, selectAllFiles, clearFileSelection, toggleAllFilesSelected } = - useFileSelection(currentPageSelectedRowModel, setCurrentPageRowSelection, paginationInfo) + const { fileSelection, selectAllFiles, clearFileSelection } = useFileSelection( + currentPageSelectedRowModel, + setCurrentPageRowSelection, + paginationInfo + ) const table = useReactTable({ data: files, - columns: createColumnsDefinition(toggleAllFilesSelected), + columns: columns, state: { rowSelection: currentPageRowSelection }, diff --git a/tests/component/sections/dataset/dataset-files/files-table/FilesTable.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/FilesTable.spec.tsx index 3da28781b..495f2b00b 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/FilesTable.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/FilesTable.spec.tsx @@ -39,7 +39,7 @@ describe('FilesTable', () => { }) describe('Row selection', () => { - it('selects all rows when the header checkbox is clicked', () => { + it('selects all rows in the current page when the header checkbox is clicked', () => { cy.customMount( ) @@ -48,25 +48,27 @@ describe('FilesTable', () => { cy.get('table > thead > tr > th > input[type=checkbox]').click() - cy.findByText('200 files are currently selected.').should('exist') + cy.findByText('10 files are currently selected.').should('exist') - cy.findByRole('button', { name: 'Select all 200 files in this dataset.' }).should('not.exist') + cy.findByRole('button', { name: 'Select all 200 files in this dataset.' }).should('exist') }) - it('clears row selection when the header checkbox is clicked', () => { + it.only('clears row selection for the current page when the header checkbox is clicked', () => { cy.customMount( ) cy.wait(1000) // wait for the table to load - cy.get('table > thead > tr > th > input[type=checkbox]').click() + cy.get('table > tbody > tr:nth-child(2) > td:nth-child(1) > input[type=checkbox]').click() + + cy.findByRole('button', { name: 'Select all 200 files in this dataset.' }).click() cy.findByText('200 files are currently selected.').should('exist') cy.get('table > thead > tr > th > input[type=checkbox]').click() - cy.findByText(/files are currently selected./).should('not.exist') + cy.findByText('190 files are currently selected.').should('exist') }) it("selects all rows when the 'Select all' button is clicked", () => {