From 09282005e88b4956ca295cbdaa5721cd350059ad Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Thu, 24 Jul 2025 16:16:13 -0400 Subject: [PATCH 1/9] feat: update use case get available categories --- docs/useCases.md | 23 ++++++++++++ .../repositories/IDatasetsRepository.ts | 1 + .../useCases/GetDatasetAvailableCategories.ts | 20 ++++++++++ src/datasets/index.ts | 5 ++- .../infra/repositories/DatasetsRepository.ts | 11 ++++++ .../domain/useCases/UpdateFileCategories.ts | 2 +- .../domain/useCases/UpdateFileTabularTags.ts | 2 +- .../GetDatasetAvailableCategories.test.ts | 37 +++++++++++++++++++ .../datasets/DatasetsRepository.test.ts | 28 ++++++++++++++ 9 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 src/datasets/domain/useCases/GetDatasetAvailableCategories.ts create mode 100644 test/functional/datasets/GetDatasetAvailableCategories.test.ts diff --git a/docs/useCases.md b/docs/useCases.md index 372704a8..b0f90d55 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -37,6 +37,7 @@ The different use cases currently available in the package are classified below, - [List All Datasets](#list-all-datasets) - [Get Dataset Versions Summaries](#get-dataset-versions-summaries) - [Get Dataset Linked Collections](#get-dataset-linked-collections) + - [Get Dataset Available Categories](#get-dataset-available-categories) - [Datasets write use cases](#datasets-write-use-cases) - [Create a Dataset](#create-a-dataset) - [Update a Dataset](#update-a-dataset) @@ -1049,6 +1050,28 @@ The `includeMDC` parameter is optional. - If MDC isn't enabled, the download count will return a total count, without `MDCStartDate`. - If MDC is enabled but the `includeMDC` is false, the count will be limited to the time before `MDCStartDate` +#### Get Dataset Available Categories + +Returns a list of available file categories that may be applied to the files of a given dataset. + +###### Example call: + +```typescript +import { getDatasetAvailableCategories } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const datasetId = 1 + +getDatasetAvailableCategories.execute(datasetId).then((categories: String[]) => { + /* ... */ +}) +``` + +_See [use case](../src/files/domain/useCases/GetDatasetAvailableCategories.ts) implementation_. + +The `datasetId` parameter is a number for numeric identifiers or string for persistent identifiers. + ## Files ### Files read use cases diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 52a6c1cd..6e55b297 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -65,4 +65,5 @@ export interface IDatasetsRepository { linkDataset(datasetId: number, collectionAlias: string): Promise unlinkDataset(datasetId: number, collectionAlias: string): Promise getDatasetLinkedCollections(datasetId: number | string): Promise + getDatasetAvailableCategories(datasetId: number | string): Promise } diff --git a/src/datasets/domain/useCases/GetDatasetAvailableCategories.ts b/src/datasets/domain/useCases/GetDatasetAvailableCategories.ts new file mode 100644 index 00000000..e51544d6 --- /dev/null +++ b/src/datasets/domain/useCases/GetDatasetAvailableCategories.ts @@ -0,0 +1,20 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { IDatasetsRepository } from '../repositories/IDatasetsRepository' + +export class GetDatasetAvailableCategories implements UseCase { + private readonly datasetsRepository: IDatasetsRepository + + constructor(datasetsRepository: IDatasetsRepository) { + this.datasetsRepository = datasetsRepository + } + + /** + * Retrieves the available file categories for a dataset. + * + * @param {number | string} [datasetId] - Persistent dataset identifier + * @returns {Promise} - List of available file categories + */ + async execute(datasetId: number | string): Promise { + return this.datasetsRepository.getDatasetAvailableCategories(datasetId) + } +} diff --git a/src/datasets/index.ts b/src/datasets/index.ts index a7a7a14b..17426cf3 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -23,6 +23,7 @@ import { DeleteDatasetDraft } from './domain/useCases/DeleteDatasetDraft' import { LinkDataset } from './domain/useCases/LinkDataset' import { UnlinkDataset } from './domain/useCases/UnlinkDataset' import { GetDatasetLinkedCollections } from './domain/useCases/GetDatasetLinkedCollections' +import { GetDatasetAvailableCategories } from './domain/useCases/GetDatasetAvailableCategories' const datasetsRepository = new DatasetsRepository() @@ -60,6 +61,7 @@ const deleteDatasetDraft = new DeleteDatasetDraft(datasetsRepository) const linkDataset = new LinkDataset(datasetsRepository) const unlinkDataset = new UnlinkDataset(datasetsRepository) const getDatasetLinkedCollections = new GetDatasetLinkedCollections(datasetsRepository) +const getDatasetAvailableCategories = new GetDatasetAvailableCategories(datasetsRepository) export { getDataset, @@ -80,7 +82,8 @@ export { deleteDatasetDraft, linkDataset, unlinkDataset, - getDatasetLinkedCollections + getDatasetLinkedCollections, + getDatasetAvailableCategories } export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion' export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions' diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 95e82b77..2464a74c 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -317,4 +317,15 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi throw error }) } + + public async getDatasetAvailableCategories(datasetId: number | string): Promise { + return this.doGet( + this.buildApiEndpoint(this.datasetsResourceName, 'availableFileCategories', datasetId), + true + ) + .then((response) => response.data.data as string[]) + .catch((error) => { + throw error + }) + } } diff --git a/src/files/domain/useCases/UpdateFileCategories.ts b/src/files/domain/useCases/UpdateFileCategories.ts index ea568073..fc2e496d 100644 --- a/src/files/domain/useCases/UpdateFileCategories.ts +++ b/src/files/domain/useCases/UpdateFileCategories.ts @@ -10,7 +10,7 @@ export class UpdateFileCategories implements UseCase { /** * Updates the categories for a particular File. - * More detailed information about updating a file's categories behavior can be found in https://guides.dataverse.org/en/latest/api/native-api.html#updating-file-metadata + * More detailed information about updating a file's categories behavior can be found in https://guides.dataverse.org/en/latest/api/native-api.html#updating-file-metadata-categories * * @param {number | string} [fileId] - The file identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers). * @param {string[]} [categories] - The categories to be added to the file. diff --git a/src/files/domain/useCases/UpdateFileTabularTags.ts b/src/files/domain/useCases/UpdateFileTabularTags.ts index 3b777a97..2f6eb543 100644 --- a/src/files/domain/useCases/UpdateFileTabularTags.ts +++ b/src/files/domain/useCases/UpdateFileTabularTags.ts @@ -10,7 +10,7 @@ export class UpdateFileTabularTags implements UseCase { /** * Updates the tabular tabular Tags for a particular File. - * More detailed information about updating a file's tabularTags behavior can be found in https://guides.dataverse.org/en/latest/api/native-api.html#updating-file-metadata + * More detailed information about updating a file's tabularTags behavior can be found in https://guides.dataverse.org/en/latest/api/native-api.html#updating-file-tabular-tags * * @param {number | string} [fileId] - The file identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers). * @param {string[]} [tabularTags] - The tabular tags to be added to the file. diff --git a/test/functional/datasets/GetDatasetAvailableCategories.test.ts b/test/functional/datasets/GetDatasetAvailableCategories.test.ts new file mode 100644 index 00000000..39c8cef2 --- /dev/null +++ b/test/functional/datasets/GetDatasetAvailableCategories.test.ts @@ -0,0 +1,37 @@ +import { ApiConfig, createDataset, getDatasetAvailableCategories, ReadError } from '../../../src' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { deleteUnpublishedDatasetViaApi } from '../../testHelpers/datasets/datasetHelper' +import { CreatedDatasetIdentifiers } from '../../../src/datasets/domain/models/CreatedDatasetIdentifiers' +import { TestConstants } from '../../testHelpers/TestConstants' + +describe('execute', () => { + let createdDatasetIdentifiers: CreatedDatasetIdentifiers + beforeEach(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + createdDatasetIdentifiers = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + }) + + afterEach(async () => { + deleteUnpublishedDatasetViaApi(createdDatasetIdentifiers.numericId) + }) + + it('should return categories array when a dataset has files categories', async () => { + const defaultCategories = ['Code', 'Data', 'Documentation'] + const categoriesList = await getDatasetAvailableCategories.execute( + createdDatasetIdentifiers.numericId + ) + expect(categoriesList.sort()).toEqual(defaultCategories.sort()) + }) + + it('should return error when dataset does not exist', async () => { + const nonExistentDatasetId = 99999 + + await expect( + getDatasetAvailableCategories.execute(nonExistentDatasetId) + ).rejects.toBeInstanceOf(ReadError) + }) +}) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 02144c15..3ef98d9e 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -1501,4 +1501,32 @@ describe('DatasetsRepository', () => { await expect(sut.getDatasetLinkedCollections(nonExistentTestDatasetId)).rejects.toThrow() }) }) + + describe('getDatasetAvailableCategories', () => { + let testDatasetIds: CreatedDatasetIdentifiers + + beforeEach(async () => { + testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + // Dataset is in draft, so we need to publish it first + await sut.publishDataset(testDatasetIds.numericId, VersionUpdateType.MAJOR) + await waitForNoLocks(testDatasetIds.numericId, 10) + }) + + test('should get available categories', async () => { + const fileMetadata = { + description: 'test description', + directoryLabel: 'directoryLabel', + categories: ['category1', 'category2', 'Documentation', 'Data', 'Code'] + } + + await uploadFileViaApi(testDatasetIds.numericId, testTextFile1Name, fileMetadata) + + const actual = await sut.getDatasetAvailableCategories(testDatasetIds.numericId) + expect(actual.sort()).toEqual(fileMetadata.categories.sort()) + }) + + test('should return error when dataset does not exist', async () => { + await expect(sut.getDatasetAvailableCategories(nonExistentTestDatasetId)).rejects.toThrow() + }) + }) }) From 7ce1c2fb3ddf9328a9ff19a61601235f73332938 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Thu, 24 Jul 2025 16:19:43 -0400 Subject: [PATCH 2/9] feat: update useCases.md --- docs/useCases.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/useCases.md b/docs/useCases.md index b0f90d55..0df4b5c5 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -65,6 +65,8 @@ The different use cases currently available in the package are classified below, - [Replace a File](#replace-a-file) - [Restrict or Unrestrict a File](#restrict-or-unrestrict-a-file) - [Update File Metadata](#update-file-metadata) + - [Update File Categories](#update-file-categories) + - [Update File Tabular Tags](#update-file-tabular-tags) - [Metadata Blocks](#metadata-blocks) - [Metadata Blocks read use cases](#metadata-blocks-read-use-cases) - [Get All Facetable Metadata Fields](#get-all-facetable-metadata-fields) From 329831e2f5d3f837dd85ccc59df1f4965efd020e Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 25 Jul 2025 10:36:54 -0400 Subject: [PATCH 3/9] Revert "fix RolesRepository integration test" This reverts commit d492fea846751ee1cdfcb43e9158ed4bbe9baa5a. --- test/testHelpers/roles/roleHelper.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/testHelpers/roles/roleHelper.ts b/test/testHelpers/roles/roleHelper.ts index 3e641cda..cf48cc3c 100644 --- a/test/testHelpers/roles/roleHelper.ts +++ b/test/testHelpers/roles/roleHelper.ts @@ -42,9 +42,7 @@ export const createSuperAdminRoleArray = (): Role[] => { 'ManageDatasetPermissions', 'ManageFilePermissions', 'PublishDataverse', - 'LinkDataverse', 'PublishDataset', - 'LinkDataset', 'DeleteDataverse', 'DeleteDatasetDraft' ], @@ -101,11 +99,10 @@ export const createSuperAdminRoleArray = (): Role[] => { 'ManageDatasetPermissions', 'ManageFilePermissions', 'PublishDataset', - 'LinkDataset', 'DeleteDatasetDraft' ], description: - 'For datasets, a person who can edit License + Terms, edit Permissions, and publish and link datasets.', + 'For datasets, a person who can edit License + Terms, edit Permissions, and publish datasets.', id: 7 }, { From 273947cecde6aacad924191351d1007695eb1778 Mon Sep 17 00:00:00 2001 From: Cheng Shi <91049239+ChengShi-1@users.noreply.github.com> Date: Fri, 25 Jul 2025 11:18:17 -0400 Subject: [PATCH 4/9] Update docs/useCases.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/useCases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/useCases.md b/docs/useCases.md index 0df4b5c5..3a6b544b 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -1070,7 +1070,7 @@ getDatasetAvailableCategories.execute(datasetId).then((categories: String[]) => }) ``` -_See [use case](../src/files/domain/useCases/GetDatasetAvailableCategories.ts) implementation_. +_See [use case](../src/datasets/domain/useCases/GetDatasetAvailableCategories.ts) implementation_. The `datasetId` parameter is a number for numeric identifiers or string for persistent identifiers. From 136e12591adacfa33adf17e32ac250dc9f84d995 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 25 Jul 2025 11:32:58 -0400 Subject: [PATCH 5/9] fix: test --- test/integration/datasets/DatasetsRepository.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 3ef98d9e..961ae6a2 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -1505,11 +1505,12 @@ describe('DatasetsRepository', () => { describe('getDatasetAvailableCategories', () => { let testDatasetIds: CreatedDatasetIdentifiers - beforeEach(async () => { + beforeAll(async () => { testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) - // Dataset is in draft, so we need to publish it first - await sut.publishDataset(testDatasetIds.numericId, VersionUpdateType.MAJOR) - await waitForNoLocks(testDatasetIds.numericId, 10) + }) + + afterAll(async () => { + await deletePublishedDatasetViaApi(testDatasetIds.persistentId) }) test('should get available categories', async () => { From 5fd4982a9972090e6088fb76b983ffa18002aa60 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Thu, 31 Jul 2025 10:25:19 -0400 Subject: [PATCH 6/9] feat: add test case for persistent id --- test/integration/datasets/DatasetsRepository.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 961ae6a2..1b86866e 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -1526,6 +1526,17 @@ describe('DatasetsRepository', () => { expect(actual.sort()).toEqual(fileMetadata.categories.sort()) }) + test('should get available categorie if dataset id is persistent id', async () => { + const fileMetadata = { + description: 'test description', + directoryLabel: 'directoryLabel', + categories: ['category1', 'category2', 'Documentation', 'Data', 'Code'] + } + + const actual = await sut.getDatasetAvailableCategories(testDatasetIds.persistentId) + expect(actual.sort()).toEqual(fileMetadata.categories.sort()) + }) + test('should return error when dataset does not exist', async () => { await expect(sut.getDatasetAvailableCategories(nonExistentTestDatasetId)).rejects.toThrow() }) From ed5feeb29bcf4f19536445a647dec817b04976ef Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 15 Aug 2025 09:39:35 -0400 Subject: [PATCH 7/9] fix: add a , --- src/datasets/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datasets/index.ts b/src/datasets/index.ts index aa13cf5c..36b8c6b3 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -85,7 +85,7 @@ export { linkDataset, unlinkDataset, getDatasetLinkedCollections, - getDatasetAvailableCategories + getDatasetAvailableCategories, getDatasetCitationInOtherFormats } export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion' From 5ec425ac0795ad5685252dfac4da620dd4d0f7bd Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 15 Aug 2025 09:47:10 -0400 Subject: [PATCH 8/9] fix: tests roleHelper --- test/testHelpers/roles/roleHelper.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/testHelpers/roles/roleHelper.ts b/test/testHelpers/roles/roleHelper.ts index 04ac0fdb..58769ed7 100644 --- a/test/testHelpers/roles/roleHelper.ts +++ b/test/testHelpers/roles/roleHelper.ts @@ -101,10 +101,11 @@ export const createSuperAdminRoleArray = (): Role[] => { 'ManageDatasetPermissions', 'ManageFilePermissions', 'PublishDataset', + 'LinkDataset', 'DeleteDatasetDraft' ], description: - 'For datasets, a person who can edit License + Terms, edit Permissions, and publish datasets.', + 'For datasets, a person who can edit License + Terms, edit Permissions, and publish datasets and link datasets.', id: 7 }, { From ace8b9de85814b2d9bb2b1768a381d6f5f62f02e Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 15 Aug 2025 09:47:57 -0400 Subject: [PATCH 9/9] fix: tests roleHelper --- test/testHelpers/roles/roleHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testHelpers/roles/roleHelper.ts b/test/testHelpers/roles/roleHelper.ts index 58769ed7..d792b377 100644 --- a/test/testHelpers/roles/roleHelper.ts +++ b/test/testHelpers/roles/roleHelper.ts @@ -105,7 +105,7 @@ export const createSuperAdminRoleArray = (): Role[] => { 'DeleteDatasetDraft' ], description: - 'For datasets, a person who can edit License + Terms, edit Permissions, and publish datasets and link datasets.', + 'For datasets, a person who can edit License + Terms, edit Permissions, and publish and link datasets.', id: 7 }, {