From 01125723bef958138840f5b72dede69f7bfab24f Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Tue, 30 Sep 2025 17:03:49 -0400 Subject: [PATCH 1/7] support review dataset type #821 --- package-lock.json | 10 ++++---- package.json | 2 +- public/locales/en/header.json | 1 + src/dataset/domain/models/Dataset.ts | 9 ++++--- .../domain/repositories/DatasetRepository.ts | 6 ++++- src/dataset/domain/useCases/createDataset.ts | 5 ++-- .../infrastructure/mappers/JSDatasetMapper.ts | 6 +++-- .../DatasetJSDataverseRepository.ts | 11 +++++--- .../MetadataBlockInfoRepository.ts | 9 +++++-- ...OnCreateMetadataBlockInfoByCollectionId.ts | 5 ++-- .../getMetadataBlockInfoByCollectionId.ts | 12 ++++++--- .../MetadataBlockInfoJSDataverseRepository.ts | 25 +++++++++++++++---- src/sections/Route.enum.ts | 1 + .../edit-dataset-menu/EditDatasetMenu.tsx | 3 +++ .../layout/header/LoggedInHeaderActions.tsx | 11 ++++++++ .../add-data-actions/AddDataActionsButton.tsx | 11 ++++++++ .../shared/form/DatasetMetadataForm/index.tsx | 9 +++++-- .../useGetMetadataBlocksInfo.tsx | 16 +++++++++--- .../DatasetMetadataForm/useSubmitDataset.ts | 8 +++++- 19 files changed, 124 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef81ff9a7..9f396bb8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@dnd-kit/sortable": "8.0.0", "@dnd-kit/utilities": "3.2.2", "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-alpha.66", + "@iqss/dataverse-client-javascript": "2.0.0-alpha.75", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -1954,14 +1954,14 @@ }, "node_modules/@iqss/dataverse-client-javascript": { "name": "@IQSS/dataverse-client-javascript", - "version": "2.0.0-alpha.66", - "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-alpha.66/5eac3e19da454f634e409469958c848b70283c16", - "integrity": "sha512-YGDUC/nk2nqmlq5DPNNbnt5KTABZAk+HCLuw90zg/8hWVhU8RSc2fRDeSuc/CQsV/NmCSw6gzhr5FsCsKitdEQ==", + "version": "2.0.0-alpha.75", + "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-alpha.75/7c205be675dd5b0bd2def5ede62f69c7b031c38f", + "integrity": "sha512-V/NZXUMCJF9K9p31EXd7EU5WSXmS4X7El2TNs0IdiVvh/oeQKXQkdmqQg8BJNC/LS/oVq6ytUT3PPl4Trjfqlw==", "license": "MIT", "dependencies": { "@types/node": "^18.15.11", "@types/turndown": "^5.0.1", - "axios": "^1.7.2", + "axios": "^1.12.2", "turndown": "^7.1.2", "typescript": "^4.9.5" } diff --git a/package.json b/package.json index c7944b54d..5f08cc39c 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@dnd-kit/sortable": "8.0.0", "@dnd-kit/utilities": "3.2.2", "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-alpha.66", + "@iqss/dataverse-client-javascript": "2.0.0-alpha.75", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", diff --git a/public/locales/en/header.json b/public/locales/en/header.json index eef91ae01..dd4e57783 100644 --- a/public/locales/en/header.json +++ b/public/locales/en/header.json @@ -8,6 +8,7 @@ "addData": "Add Data", "newCollection": "New Collection", "newDataset": "New Dataset", + "newReview": "New Review", "accountInfo": "Account Information", "apiToken": "API Token", "myData": "My Data" diff --git a/src/dataset/domain/models/Dataset.ts b/src/dataset/domain/models/Dataset.ts index 30e10ea5f..b5e8f9e8d 100644 --- a/src/dataset/domain/models/Dataset.ts +++ b/src/dataset/domain/models/Dataset.ts @@ -430,7 +430,8 @@ export class Dataset { public readonly nextMajorVersion?: string, public readonly nextMinorVersion?: string, public readonly requiresMajorVersionUpdate?: boolean, - public readonly fileStore?: string + public readonly fileStore?: string, + public readonly datasetType?: string ) {} public checkIsLockedFromPublishing(userPersistentId: string): boolean { @@ -525,7 +526,8 @@ export class Dataset { public readonly nextMajorVersionNumber?: string, public readonly nextMinorVersionNumber?: string, public readonly requiresMajorVersionUpdate?: boolean, - public readonly fileStore?: string + public readonly fileStore?: string, + public readonly datasetType?: string ) { this.withAlerts() } @@ -597,7 +599,8 @@ export class Dataset { this.nextMajorVersionNumber, this.nextMinorVersionNumber, this.requiresMajorVersionUpdate, - this.fileStore + this.fileStore, + this.datasetType ) } } diff --git a/src/dataset/domain/repositories/DatasetRepository.ts b/src/dataset/domain/repositories/DatasetRepository.ts index b87314506..7568591bc 100644 --- a/src/dataset/domain/repositories/DatasetRepository.ts +++ b/src/dataset/domain/repositories/DatasetRepository.ts @@ -26,7 +26,11 @@ export interface DatasetRepository { includeDeaccessioned: boolean ) => Promise - create: (dataset: DatasetDTO, collectionId: string) => Promise<{ persistentId: string }> + create: ( + dataset: DatasetDTO, + collectionId: string, + datasetType?: string + ) => Promise<{ persistentId: string }> updateMetadata: ( datasetId: string | number, datasetDTO: DatasetDTO, diff --git a/src/dataset/domain/useCases/createDataset.ts b/src/dataset/domain/useCases/createDataset.ts index abe0a943f..bc231855c 100644 --- a/src/dataset/domain/useCases/createDataset.ts +++ b/src/dataset/domain/useCases/createDataset.ts @@ -4,9 +4,10 @@ import { DatasetDTO } from './DTOs/DatasetDTO' export function createDataset( datasetRepository: DatasetRepository, dataset: DatasetDTO, - collectionId: string + collectionId: string, + datasetType?: string ): Promise<{ persistentId: string }> { - return datasetRepository.create(dataset, collectionId).catch((error: Error) => { + return datasetRepository.create(dataset, collectionId, datasetType).catch((error: Error) => { throw new Error(error.message) }) } diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index edb9d5a70..5f020b410 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -49,7 +49,8 @@ export class JSDatasetMapper { latestPublishedVersionMajorNumber?: number, latestPublishedVersionMinorNumber?: number, datasetVersionDiff?: JSDatasetVersionDiff, - fileStore?: string + fileStore?: string, + datasetType?: string ): Dataset { const version = JSDatasetVersionMapper.toVersion( jsDataset.versionId, @@ -99,7 +100,8 @@ export class JSDatasetMapper { latestPublishedVersionMinorNumber ), JSDatasetMapper.toRequiresMajorVersionUpdate(datasetVersionDiff), - fileStore + fileStore, + datasetType ).build() } diff --git a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts index 8d7d73d51..91bcab470 100644 --- a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts +++ b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts @@ -254,7 +254,8 @@ export class DatasetJSDataverseRepository implements DatasetRepository { datasetDetails.latestPublishedVersionMajorNumber, datasetDetails.latestPublishedVersionMinorNumber, datasetDetails.datasetVersionDiff, - datasetDetails.fileStore + datasetDetails.fileStore, + datasetDetails.jsDataset.datasetType ) }) .catch((error: ReadError) => { @@ -312,9 +313,13 @@ export class DatasetJSDataverseRepository implements DatasetRepository { }) } - create(dataset: DatasetDTO, collectionId: string): Promise<{ persistentId: string }> { + create( + dataset: DatasetDTO, + collectionId: string, + datasetType?: string + ): Promise<{ persistentId: string }> { return createDataset - .execute(DatasetDTOMapper.toJSDatasetDTO(dataset), collectionId) + .execute(DatasetDTOMapper.toJSDatasetDTO(dataset), collectionId, datasetType) .then((jsDatasetIdentifiers: JSDatasetIdentifiers) => ({ persistentId: jsDatasetIdentifiers.persistentId })) diff --git a/src/metadata-block-info/domain/repositories/MetadataBlockInfoRepository.ts b/src/metadata-block-info/domain/repositories/MetadataBlockInfoRepository.ts index 4b5044051..95553cf0e 100644 --- a/src/metadata-block-info/domain/repositories/MetadataBlockInfoRepository.ts +++ b/src/metadata-block-info/domain/repositories/MetadataBlockInfoRepository.ts @@ -8,8 +8,13 @@ export interface MetadataBlockInfoRepository { getByName: (name: string) => Promise getAll: () => Promise getDisplayedOnCreateByCollectionId: ( - collectionId: number | string + collectionId: number | string, + datasetType?: string + ) => Promise + getByCollectionId: ( + collectionId: number | string, + onlyDisplayedOnCreate?: boolean, + datasetType?: string ) => Promise - getByCollectionId: (collectionId: number | string) => Promise getAllFacetableMetadataFields: () => Promise } diff --git a/src/metadata-block-info/domain/useCases/getDisplayedOnCreateMetadataBlockInfoByCollectionId.ts b/src/metadata-block-info/domain/useCases/getDisplayedOnCreateMetadataBlockInfoByCollectionId.ts index 3fede920f..da9c676c8 100644 --- a/src/metadata-block-info/domain/useCases/getDisplayedOnCreateMetadataBlockInfoByCollectionId.ts +++ b/src/metadata-block-info/domain/useCases/getDisplayedOnCreateMetadataBlockInfoByCollectionId.ts @@ -3,10 +3,11 @@ import { MetadataBlockInfoRepository } from '../repositories/MetadataBlockInfoRe export async function getDisplayedOnCreateMetadataBlockInfoByCollectionId( metadataBlockInfoRepository: MetadataBlockInfoRepository, - collectionId: number | string + collectionId: number | string, + datasetType?: string ): Promise { return metadataBlockInfoRepository - .getDisplayedOnCreateByCollectionId(collectionId) + .getDisplayedOnCreateByCollectionId(collectionId, datasetType) .catch((error: Error) => { throw new Error(error.message) }) diff --git a/src/metadata-block-info/domain/useCases/getMetadataBlockInfoByCollectionId.ts b/src/metadata-block-info/domain/useCases/getMetadataBlockInfoByCollectionId.ts index ab3fba345..5fe7becdf 100644 --- a/src/metadata-block-info/domain/useCases/getMetadataBlockInfoByCollectionId.ts +++ b/src/metadata-block-info/domain/useCases/getMetadataBlockInfoByCollectionId.ts @@ -3,9 +3,13 @@ import { MetadataBlockInfoRepository } from '../repositories/MetadataBlockInfoRe export async function getMetadataBlockInfoByCollectionId( metadataBlockInfoRepository: MetadataBlockInfoRepository, - collectionId: number | string + collectionId: number | string, + onlyDisplayedOnCreate?: boolean, + datasetType?: string ): Promise { - return metadataBlockInfoRepository.getByCollectionId(collectionId).catch((error: Error) => { - throw new Error(error.message) - }) + return metadataBlockInfoRepository + .getByCollectionId(collectionId, onlyDisplayedOnCreate, datasetType) + .catch((error: Error) => { + throw new Error(error.message) + }) } diff --git a/src/metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository.ts b/src/metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository.ts index baa46e3d3..fb49676da 100644 --- a/src/metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository.ts +++ b/src/metadata-block-info/infrastructure/repositories/MetadataBlockInfoJSDataverseRepository.ts @@ -37,9 +37,13 @@ export class MetadataBlockInfoJSDataverseRepository implements MetadataBlockInfo }) } - getByCollectionId(collectionIdOrAlias: number | string): Promise { + getByCollectionId( + collectionIdOrAlias: number | string, + onlyDisplayedOnCreate?: boolean, + datasetType?: string + ): Promise { return getCollectionMetadataBlocks - .execute(collectionIdOrAlias) + .execute(collectionIdOrAlias, onlyDisplayedOnCreate, datasetType) .then((metadataBlocks: MetadataBlockInfo[]) => { return metadataBlocks }) @@ -49,12 +53,23 @@ export class MetadataBlockInfoJSDataverseRepository implements MetadataBlockInfo } getDisplayedOnCreateByCollectionId( - collectionIdOrAlias: number | string + collectionIdOrAlias: number | string, + datasetType?: string ): Promise { return getCollectionMetadataBlocks - .execute(collectionIdOrAlias, true) + .execute(collectionIdOrAlias, true, datasetType) .then((metadataBlocks: MetadataBlockInfo[]) => { - return metadataBlocks + const metadataBlocksWithFields: MetadataBlockInfo[] = [] + metadataBlocks.forEach((block) => { + const numFields = Object.keys(block.metadataFields).length + // numFields can be zero if you pass a datasetType that's linked to + // a metadata block that doesn't have any fields set to displayOnCreate. + // See https://github.com/IQSS/dataverse/blob/v6.7.1/src/test/java/edu/harvard/iq/dataverse/api/DatasetTypesIT.java#L512 + if (numFields > 0) { + metadataBlocksWithFields.push(block) + } + }) + return metadataBlocksWithFields }) .catch((error: ReadError) => { throw new Error(error.message) diff --git a/src/sections/Route.enum.ts b/src/sections/Route.enum.ts index 2adbe8334..e7de6b546 100644 --- a/src/sections/Route.enum.ts +++ b/src/sections/Route.enum.ts @@ -79,6 +79,7 @@ export enum QueryParamKey { TAB = 'tab', FILE_ID = 'id', DATASET_VERSION = 'datasetVersion', + DATASET_TYPE = 'datasetType', REFERRER = 'referrer', AUTH_STATE = 'state', VALID_TOKEN_BUT_NOT_LINKED_ACCOUNT = 'validTokenButNotLinkedAccount' diff --git a/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx index 043b5cd05..7458bda69 100644 --- a/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx @@ -40,6 +40,9 @@ export function EditDatasetMenu({ dataset, datasetRepository }: EditDatasetMenuP const handleOnSelect = (eventKey: EditDatasetMenuItems | string | null) => { const searchParams = new URLSearchParams() searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) + if (dataset.datasetType) { + searchParams.set(QueryParamKey.DATASET_TYPE, dataset.datasetType) + } if (dataset.version.publishingStatus === DatasetPublishingStatus.DRAFT) { searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) diff --git a/src/sections/layout/header/LoggedInHeaderActions.tsx b/src/sections/layout/header/LoggedInHeaderActions.tsx index 5113a2ddb..0d4c3e47a 100644 --- a/src/sections/layout/header/LoggedInHeaderActions.tsx +++ b/src/sections/layout/header/LoggedInHeaderActions.tsx @@ -38,6 +38,9 @@ export const LoggedInHeaderActions = ({ const canUserAddCollectionToRoot = Boolean(collectionUserPermissions?.canAddCollection) const canUserAddDatasetToRoot = Boolean(collectionUserPermissions?.canAddDataset) + // Enable/disable review. See also LoggedInHeaderActions.tsx + // TODO: make dynamic based on getDatasetAvailableDatasetTypes + const reviewCreationEnabled = false return ( <> @@ -51,6 +54,14 @@ export const LoggedInHeaderActions = ({ {t('navigation.newDataset')} + {reviewCreationEnabled && ( + + {t('navigation.newReview')} + + )} {t('navigation.newDataset')} + {reviewCreationEnabled && ( + + {t('navigation.newReview')} + + )} ) } diff --git a/src/sections/shared/form/DatasetMetadataForm/index.tsx b/src/sections/shared/form/DatasetMetadataForm/index.tsx index 548e6c62a..eca3226e7 100644 --- a/src/sections/shared/form/DatasetMetadataForm/index.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/index.tsx @@ -20,6 +20,7 @@ type DatasetMetadataFormProps = datasetMetadaBlocksCurrentValues?: never datasetInternalVersionNumber?: never datasetTemplate?: DatasetTemplate + datasetType?: string } | { mode: 'edit' @@ -30,6 +31,8 @@ type DatasetMetadataFormProps = datasetMetadaBlocksCurrentValues: DatasetMetadataBlocks datasetInternalVersionNumber: number datasetTemplate?: never + // changing datasetType is not supported by the backend + datasetType?: string } export type DatasetMetadataFormMode = 'create' | 'edit' @@ -42,7 +45,8 @@ export const DatasetMetadataForm = ({ metadataBlockInfoRepository, datasetMetadaBlocksCurrentValues, datasetInternalVersionNumber, - datasetTemplate + datasetTemplate, + datasetType }: DatasetMetadataFormProps) => { const { setIsLoading } = useLoading() @@ -53,7 +57,8 @@ export const DatasetMetadataForm = ({ } = useGetMetadataBlocksInfo({ mode: 'create', collectionId, - metadataBlockInfoRepository + metadataBlockInfoRepository, + datasetType }) const { diff --git a/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx b/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx index 27e5ce567..d1e5a0a06 100644 --- a/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx @@ -9,6 +9,7 @@ interface Props { mode: DatasetMetadataFormMode collectionId: string metadataBlockInfoRepository: MetadataBlockInfoRepository + datasetType?: string } interface UseGetMetadataBlocksInfoReturn { @@ -20,7 +21,8 @@ interface UseGetMetadataBlocksInfoReturn { export const useGetMetadataBlocksInfo = ({ mode, collectionId, - metadataBlockInfoRepository + metadataBlockInfoRepository, + datasetType }: Props): UseGetMetadataBlocksInfoReturn => { const [metadataBlocksInfo, setMetadataBlocksInfo] = useState([]) const [isLoading, setIsLoading] = useState(true) @@ -32,15 +34,23 @@ export const useGetMetadataBlocksInfo = ({ try { let metadataBlocks: MetadataBlockInfo[] = [] + const urlParams = new URLSearchParams(window.location.search) + const datasetTypeIn = urlParams.get('datasetType') + if (datasetTypeIn) { + datasetType = datasetTypeIn + } if (mode === 'edit') { metadataBlocks = await getMetadataBlockInfoByCollectionId( metadataBlockInfoRepository, - collectionId + collectionId, + false, + datasetType ) } else { metadataBlocks = await getDisplayedOnCreateMetadataBlockInfoByCollectionId( metadataBlockInfoRepository, - collectionId + collectionId, + datasetType ) } diff --git a/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts b/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts index c10f6eafb..e85fe7fa9 100644 --- a/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts +++ b/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts @@ -61,7 +61,13 @@ export function useSubmitDataset( ) if (mode === 'create') { - createDataset(datasetRepository, formattedFormValues, collectionId) + let datasetType = 'dataset' + const urlParams = new URLSearchParams(window.location.search) + const datasetTypeIn = urlParams.get('datasetType') + if (datasetTypeIn) { + datasetType = datasetTypeIn + } + createDataset(datasetRepository, formattedFormValues, collectionId, datasetType) .then(({ persistentId }) => { setSubmitError(null) setSubmissionStatus(SubmissionStatus.SubmitComplete) From e41580f955264c161fa8817ae166185ab54e6377 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Thu, 2 Oct 2025 14:44:57 -0400 Subject: [PATCH 2/7] fix tests in JSDatasetMapper.spec.ts #821 --- .../dataset/infrastructure/mappers/JSDatasetMapper.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts index ef11e886c..1f02543cf 100644 --- a/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts +++ b/tests/component/dataset/infrastructure/mappers/JSDatasetMapper.spec.ts @@ -215,6 +215,7 @@ const jsDatasetFilesTotalArchivalDownloadSize = 7 const expectedDataset = { id: 505, persistentId: 'doi:10.5072/FK2/B4B2MJ', + datasetType: undefined, version: { id: 101, title: "Darwin's Finches", @@ -334,6 +335,7 @@ const expectedDataset = { const expectedDatasetWithPublicationDate = { id: 505, persistentId: 'doi:10.5072/FK2/B4B2MJ', + datasetType: undefined, version: { id: 101, title: "Darwin's Finches", @@ -451,6 +453,7 @@ const expectedDatasetWithPublicationDate = { const expectedDatasetWithNextVersionNumbers = { id: 505, persistentId: 'doi:10.5072/FK2/B4B2MJ', + datasetType: undefined, version: { id: 101, title: "Darwin's Finches", @@ -569,6 +572,7 @@ const expectedDatasetWithNextVersionNumbers = { const expectedDatasetAlternateVersion = { id: 505, persistentId: 'doi:10.5072/FK2/B4B2MJ', + datasetType: undefined, version: { id: 101, title: "Darwin's Finches", From 6b30968792a377bcd25888f10ce199ae1b797225 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Fri, 3 Oct 2025 14:13:40 -0400 Subject: [PATCH 3/7] exercise dropdown under "Edit Dataset" button #821 --- .../dataset/domain/models/DatasetMother.ts | 4 ++- .../DatasetActionButtons.spec.tsx | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/component/dataset/domain/models/DatasetMother.ts b/tests/component/dataset/domain/models/DatasetMother.ts index 661a2e6e9..387d9b388 100644 --- a/tests/component/dataset/domain/models/DatasetMother.ts +++ b/tests/component/dataset/domain/models/DatasetMother.ts @@ -341,6 +341,7 @@ export class DatasetMother { const dataset = { id: faker.datatype.number(), persistentId: faker.datatype.uuid(), + datasetType: 'dataset', version: DatasetVersionMother.create(), internalVersionNumber: faker.datatype.number(), license: { @@ -450,7 +451,8 @@ export class DatasetMother { dataset.nextMajorVersion, dataset.nextMinorVersion, dataset.requiresMajorVersionUpdate, - dataset.fileStore + dataset.fileStore, + dataset.datasetType ).build() } diff --git a/tests/component/sections/dataset/dataset-action-buttons/DatasetActionButtons.spec.tsx b/tests/component/sections/dataset/dataset-action-buttons/DatasetActionButtons.spec.tsx index aef796ff7..ea14400b4 100644 --- a/tests/component/sections/dataset/dataset-action-buttons/DatasetActionButtons.spec.tsx +++ b/tests/component/sections/dataset/dataset-action-buttons/DatasetActionButtons.spec.tsx @@ -73,6 +73,33 @@ describe('DatasetActionButtons', () => { cy.findByRole('button', { name: 'Share' }).should('exist') }) + it('renders the DatasetActionButtons with the Edit Dataset button and dropdown', () => { + const dataset = DatasetMother.create({ + version: DatasetVersionMother.createDraftAsLatestVersionWithSomeVersionHasBeenReleased(), + permissions: DatasetPermissionsMother.create({ + canDownloadFiles: true, + canUpdateDataset: true, + canPublishDataset: false + }), + fileDownloadSizes: [ + DatasetFileDownloadSizeMother.createOriginal({ value: 2000, unit: FileSizeUnit.BYTES }) + ] + }) + + cy.mountAuthenticated( + + ) + + cy.findByRole('group', { name: 'Dataset Action Buttons' }).should('exist') + cy.findByRole('button', { name: 'Edit Dataset' }).click() + cy.findByRole('button', { name: 'Metadata' }).click() + }) + it('should not render Share button if the the dataset is deaccessioned and user has no edit permission', () => { const dataset = DatasetMother.createDeaccessionedwithNoEditPermission() cy.mountAuthenticated( From 1042ec0a86dc4c882d37714d4d87ea3c162dfcc2 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Mon, 6 Oct 2025 13:59:41 -0400 Subject: [PATCH 4/7] switch from URLSearchParams to useSearchParams and tweak test #821 --- .../shared/form/DatasetMetadataForm/useSubmitDataset.ts | 7 ++++--- .../dataset-metadata-form/DatasetMetadataForm.spec.tsx | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts b/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts index e85fe7fa9..589ef3466 100644 --- a/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts +++ b/src/sections/shared/form/DatasetMetadataForm/useSubmitDataset.ts @@ -1,6 +1,6 @@ import { useState } from 'react' import { toast } from 'react-toastify' -import { useNavigate } from 'react-router-dom' +import { useNavigate, useSearchParams } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { DatasetRepository } from '../../../../dataset/domain/repositories/DatasetRepository' import { createDataset } from '../../../../dataset/domain/useCases/createDataset' @@ -50,6 +50,8 @@ export function useSubmitDataset( ) const [submitError, setSubmitError] = useState(null) + const [searchParams] = useSearchParams() + const submitForm = (formData: DatasetMetadataFormValues): void => { setSubmissionStatus(SubmissionStatus.IsSubmitting) @@ -62,8 +64,7 @@ export function useSubmitDataset( if (mode === 'create') { let datasetType = 'dataset' - const urlParams = new URLSearchParams(window.location.search) - const datasetTypeIn = urlParams.get('datasetType') + const datasetTypeIn = searchParams.get(QueryParamKey.DATASET_TYPE) if (datasetTypeIn) { datasetType = datasetTypeIn } diff --git a/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx b/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx index b1b6c0ac9..d86912490 100644 --- a/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx +++ b/tests/component/sections/shared/dataset-metadata-form/DatasetMetadataForm.spec.tsx @@ -9,6 +9,7 @@ import { DatasetMother } from '../../../dataset/domain/models/DatasetMother' import { MetadataBlockInfoMother } from '../../../metadata-block-info/domain/models/MetadataBlockInfoMother' import { UserMother } from '../../../users/domain/models/UserMother' import { DatasetTemplateMother } from '@tests/component/dataset/domain/models/DatasetTemplateMother' +import { Route } from '@/sections/Route.enum' const datasetRepository: DatasetRepository = {} as DatasetRepository const metadataBlockInfoRepository: MetadataBlockInfoRepository = {} as MetadataBlockInfoRepository @@ -1283,7 +1284,8 @@ describe('DatasetMetadataForm', () => { collectionId="root" datasetRepository={datasetRepository} metadataBlockInfoRepository={metadataBlockInfoRepository} - /> + />, + [`${Route.CREATE_DATASET}?datasetType=dataset`] ) fillRequiredFieldsOnCreate() cy.findByText(/Save Dataset/i).click() From 7d6fd67c4cc5230a5e04bf3906c87351a7211ff4 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Mon, 6 Oct 2025 14:15:05 -0400 Subject: [PATCH 5/7] replace URLSearchParams with useSearchParams #821 --- .../form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx b/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx index d1e5a0a06..8f4f30549 100644 --- a/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx +++ b/src/sections/shared/form/DatasetMetadataForm/useGetMetadataBlocksInfo.tsx @@ -4,6 +4,8 @@ import { getDisplayedOnCreateMetadataBlockInfoByCollectionId } from '../../../.. import { MetadataBlockInfoRepository } from '../../../../metadata-block-info/domain/repositories/MetadataBlockInfoRepository' import { MetadataBlockInfo } from '../../../../metadata-block-info/domain/models/MetadataBlockInfo' import { DatasetMetadataFormMode } from '.' +import { useSearchParams } from 'react-router-dom' +import { QueryParamKey } from '@/sections/Route.enum' interface Props { mode: DatasetMetadataFormMode @@ -27,6 +29,7 @@ export const useGetMetadataBlocksInfo = ({ const [metadataBlocksInfo, setMetadataBlocksInfo] = useState([]) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) + const [searchParams] = useSearchParams() useEffect(() => { const handleGetDatasetMetadataBlockFields = async () => { @@ -34,8 +37,7 @@ export const useGetMetadataBlocksInfo = ({ try { let metadataBlocks: MetadataBlockInfo[] = [] - const urlParams = new URLSearchParams(window.location.search) - const datasetTypeIn = urlParams.get('datasetType') + const datasetTypeIn = searchParams.get(QueryParamKey.DATASET_TYPE) if (datasetTypeIn) { datasetType = datasetTypeIn } From ecf5ac9d949126f9958e5546ed184b59f2712b42 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Wed, 8 Oct 2025 10:25:04 -0400 Subject: [PATCH 6/7] retrieve dataset types from API instead of hard coding #821 --- .../hooks/useGetAvailableDatasetTypes.ts | 56 +++++++++++++++++++ src/dataset/domain/models/DatasetType.ts | 6 ++ .../domain/repositories/DatasetRepository.ts | 2 + .../useCases/getAvailableDatasetTypes.ts | 8 +++ .../DatasetJSDataverseRepository.ts | 7 ++- src/sections/collection/Collection.tsx | 6 +- src/sections/collection/CollectionFactory.tsx | 3 + src/sections/layout/header/Header.tsx | 10 +++- src/sections/layout/header/HeaderFactory.tsx | 6 +- .../layout/header/LoggedInHeaderActions.tsx | 28 +++++++--- .../add-data-actions/AddDataActionsButton.tsx | 25 ++++++--- src/stories/collection/Collection.stories.tsx | 8 +++ .../CollectionItemsPanel.stories.tsx | 15 ++++- .../dataset/DatasetErrorMockRepository.ts | 9 +++ src/stories/dataset/DatasetMockRepository.ts | 10 ++++ src/stories/layout/header/Header.stories.tsx | 15 ++++- .../AddDataActionsButton.stories.tsx | 10 +++- .../domain/models/DatasetTypeMother.ts | 15 +++++ .../header/LoggedInHeaderActions.spec.tsx | 32 +++++++++-- 19 files changed, 240 insertions(+), 31 deletions(-) create mode 100644 src/dataset/domain/hooks/useGetAvailableDatasetTypes.ts create mode 100644 src/dataset/domain/models/DatasetType.ts create mode 100644 src/dataset/domain/useCases/getAvailableDatasetTypes.ts create mode 100644 tests/component/dataset/domain/models/DatasetTypeMother.ts diff --git a/src/dataset/domain/hooks/useGetAvailableDatasetTypes.ts b/src/dataset/domain/hooks/useGetAvailableDatasetTypes.ts new file mode 100644 index 000000000..16f6e34bd --- /dev/null +++ b/src/dataset/domain/hooks/useGetAvailableDatasetTypes.ts @@ -0,0 +1,56 @@ +import { useCallback, useEffect, useState } from 'react' +import { ReadError } from '@iqss/dataverse-client-javascript' +import { JSDataverseReadErrorHandler } from '@/shared/helpers/JSDataverseReadErrorHandler' +import { DatasetRepository } from '../repositories/DatasetRepository' +import { getAvailableDatasetTypes } from '../useCases/getAvailableDatasetTypes' +import { DatasetType } from '../models/DatasetType' + +interface useGetAvailableDatasetTypesProps { + datasetRepository: DatasetRepository + autoFetch?: boolean +} + +export const useGetAvailableDatasetTypes = ({ + datasetRepository, + autoFetch = true +}: useGetAvailableDatasetTypesProps) => { + const [datasetTypes, setDatasetTypes] = useState([]) + const [isLoadingDatasetTypes, setIsLoadingDatasetTypes] = useState(autoFetch) + const [errorGetDatasetTypes, setErrorGetDatasetTypes] = useState(null) + + const fetchDatasetTypes = useCallback(async () => { + setIsLoadingDatasetTypes(true) + setErrorGetDatasetTypes(null) + + try { + const response: DatasetType[] = await getAvailableDatasetTypes(datasetRepository) + + setDatasetTypes(response) + } catch (err) { + if (err instanceof ReadError) { + const error = new JSDataverseReadErrorHandler(err) + const formattedError = + error.getReasonWithoutStatusCode() ?? /* istanbul ignore next */ error.getErrorMessage() + + setErrorGetDatasetTypes(formattedError) + } else { + setErrorGetDatasetTypes('Something went wrong getting the dataset types. Try again later.') + } + } finally { + setIsLoadingDatasetTypes(false) + } + }, [datasetRepository]) + + useEffect(() => { + if (autoFetch) { + void fetchDatasetTypes() + } + }, [autoFetch, fetchDatasetTypes]) + + return { + datasetTypes, + isLoadingDatasetTypes, + errorGetDatasetTypes, + fetchDatasetTypes + } +} diff --git a/src/dataset/domain/models/DatasetType.ts b/src/dataset/domain/models/DatasetType.ts new file mode 100644 index 000000000..56a5ed436 --- /dev/null +++ b/src/dataset/domain/models/DatasetType.ts @@ -0,0 +1,6 @@ +export interface DatasetType { + id?: number + name: string + linkedMetadataBlocks?: string[] + availableLicenses?: string[] +} diff --git a/src/dataset/domain/repositories/DatasetRepository.ts b/src/dataset/domain/repositories/DatasetRepository.ts index 7568591bc..27942a941 100644 --- a/src/dataset/domain/repositories/DatasetRepository.ts +++ b/src/dataset/domain/repositories/DatasetRepository.ts @@ -9,6 +9,7 @@ import { DatasetDeaccessionDTO } from '../useCases/DTOs/DatasetDTO' import { DatasetDownloadCount } from '../models/DatasetDownloadCount' import { FormattedCitation, CitationFormat } from '../models/DatasetCitation' import { DatasetTemplate } from '../models/DatasetTemplate' +import { DatasetType } from '../models/DatasetType' export interface DatasetRepository { getByPersistentId: ( @@ -59,5 +60,6 @@ export interface DatasetRepository { version: string, format: CitationFormat ) => Promise + getAvailableDatasetTypes: () => Promise getTemplates: (collectionIdOrAlias: number | string) => Promise } diff --git a/src/dataset/domain/useCases/getAvailableDatasetTypes.ts b/src/dataset/domain/useCases/getAvailableDatasetTypes.ts new file mode 100644 index 000000000..926a42f58 --- /dev/null +++ b/src/dataset/domain/useCases/getAvailableDatasetTypes.ts @@ -0,0 +1,8 @@ +import { DatasetRepository } from '../repositories/DatasetRepository' +import { DatasetType } from '@iqss/dataverse-client-javascript' + +export function getAvailableDatasetTypes( + datasetRepository: DatasetRepository +): Promise { + return datasetRepository.getAvailableDatasetTypes() +} diff --git a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts index 91bcab470..08badfaa2 100644 --- a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts +++ b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts @@ -34,7 +34,9 @@ import { deleteDatasetDraft, getDatasetCitationInOtherFormats, getDatasetAvailableCategories, - getDatasetTemplates + getDatasetTemplates, + getDatasetAvailableDatasetTypes, + DatasetType } from '@iqss/dataverse-client-javascript' import { JSDatasetMapper } from '../mappers/JSDatasetMapper' import { DatasetPaginationInfo } from '../../domain/models/DatasetPaginationInfo' @@ -402,6 +404,9 @@ export class DatasetJSDataverseRepository implements DatasetRepository { getAvailableCategories(datasetId: string | number): Promise { return getDatasetAvailableCategories.execute(datasetId) } + getAvailableDatasetTypes(): Promise { + return getDatasetAvailableDatasetTypes.execute() + } getTemplates(collectionIdOrAlias: number | string): Promise { return getDatasetTemplates.execute(collectionIdOrAlias) diff --git a/src/sections/collection/Collection.tsx b/src/sections/collection/Collection.tsx index 7dc0f5c27..1ed9aaedc 100644 --- a/src/sections/collection/Collection.tsx +++ b/src/sections/collection/Collection.tsx @@ -22,6 +22,7 @@ import { CollectionHelper } from './CollectionHelper' import { ContactRepository } from '@/contact/domain/repositories/ContactRepository' import { NotFoundPage } from '../not-found-page/NotFoundPage' import styles from './Collection.module.scss' +import { DatasetRepository } from '@/dataset/domain/repositories/DatasetRepository' interface CollectionProps { collectionRepository: CollectionRepository @@ -31,6 +32,7 @@ interface CollectionProps { accountCreated: boolean infiniteScrollEnabled?: boolean contactRepository: ContactRepository + datasetRepository: DatasetRepository } export function Collection({ @@ -39,7 +41,8 @@ export function Collection({ created, collectionQueryParams, contactRepository, - accountCreated + accountCreated, + datasetRepository }: CollectionProps) { useScrollTop() const { previousPath } = useHistoryTracker() @@ -142,6 +145,7 @@ export function Collection({ collectionId={collection.id} canAddCollection={canUserAddCollection} canAddDataset={canUserAddDataset} + datasetRepository={datasetRepository} /> ) : null } diff --git a/src/sections/collection/CollectionFactory.tsx b/src/sections/collection/CollectionFactory.tsx index 8f4d97a9c..f34e9a479 100644 --- a/src/sections/collection/CollectionFactory.tsx +++ b/src/sections/collection/CollectionFactory.tsx @@ -6,9 +6,11 @@ import { Collection } from './Collection' import { INFINITE_SCROLL_ENABLED } from './config' import { useGetCollectionQueryParams } from './useGetCollectionQueryParams' import { ACCOUNT_CREATED_SESSION_STORAGE_KEY } from './AccountCreatedAlert' +import { DatasetJSDataverseRepository } from '@/dataset/infrastructure/repositories/DatasetJSDataverseRepository' const collectionRepository = new CollectionJSDataverseRepository() const contactRepository = new ContactJSDataverseRepository() +const datasetRepository = new DatasetJSDataverseRepository() export class CollectionFactory { static create(): ReactElement { @@ -39,6 +41,7 @@ function CollectionWithSearchParams() { accountCreated={accountCreated} infiniteScrollEnabled={INFINITE_SCROLL_ENABLED} contactRepository={contactRepository} + datasetRepository={datasetRepository} /> ) } diff --git a/src/sections/layout/header/Header.tsx b/src/sections/layout/header/Header.tsx index 95edfde45..f46d5bae3 100644 --- a/src/sections/layout/header/Header.tsx +++ b/src/sections/layout/header/Header.tsx @@ -10,11 +10,13 @@ import { LoggedInHeaderActions } from './LoggedInHeaderActions' import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository' import { encodeReturnToPathInStateQueryParam } from '@/sections/auth-callback/AuthCallback' import styles from './Header.module.scss' +import { DatasetRepository } from '@/dataset/domain/repositories/DatasetRepository' interface HeaderProps { collectionRepository: CollectionRepository + datasetRepository: DatasetRepository } -export function Header({ collectionRepository }: HeaderProps) { +export function Header({ collectionRepository, datasetRepository }: HeaderProps) { const { t } = useTranslation('header') const { user } = useSession() const { pathname, search } = useLocation() @@ -36,7 +38,11 @@ export function Header({ collectionRepository }: HeaderProps) { }} className={styles.navbar}> {user ? ( - + ) : (