From 20f759c332e7e966f12d97f4f3bd1d6400ecc609 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 4 Mar 2025 16:49:19 -0500 Subject: [PATCH 1/9] feat: use case for dataset download count --- docs/useCases.md | 23 +++++ .../repositories/IDatasetsRepository.ts | 1 + .../useCases/GetDatasetDownloadCount.ts | 20 ++++ src/datasets/index.ts | 5 +- .../infra/repositories/DatasetsRepository.ts | 14 +++ .../datasets/GetDatasetDownloadCount.test.ts | 98 +++++++++++++++++++ .../datasets/DatasetsRepository.test.ts | 37 +++++++ .../datasets/GetDatasetDownloadCount.test.ts | 30 ++++++ 8 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 src/datasets/domain/useCases/GetDatasetDownloadCount.ts create mode 100644 test/functional/datasets/GetDatasetDownloadCount.test.ts create mode 100644 test/unit/datasets/GetDatasetDownloadCount.test.ts diff --git a/docs/useCases.md b/docs/useCases.md index 47e09f6c..76e7bfe7 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -807,6 +807,29 @@ The `version` parameter should be a string or a [DatasetNotNumberedVersion](../s You cannot deaccession a dataset more than once. If you call this endpoint twice for the same dataset version, you will get a not found error on the second call, since the dataset you are looking for will no longer be published since it is already deaccessioned. +#### Get Download Count of a Dataset + +Total number of downloads requested for a dataset, given a dataset numeric identifier, + +##### Example call: + +```typescript +import { getDatasetDownloadCount } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const datasetId = 1 +const includeMDC = true + +getDatasetDownloadCount.execute(datasetId, includeMDC) + +/* ... */ +``` + +_See [use case](../src/datasets/domain/useCases/GetDatasetDownloadCount.ts) implementation_. +The `datasetId` parameter is a number for numeric identifiers. +The `includeMDC` parameter is optional. If MDC is enabled the count will be limited to the time before MDC start if the optional `includeMDC` parameter is not included or set to False. Setting `includeMDC` to True will ignore the `:MDCStartDate` setting and return a total count. + ## Files ### Files read use cases diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index dd2b954d..9d035a59 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -51,4 +51,5 @@ export interface IDatasetsRepository { datasetVersionId: string, deaccessionDTO: DatasetDeaccessionDTO ): Promise + getDatasetDownloadCount(datasetId: number, includeMDC?: boolean): Promise } diff --git a/src/datasets/domain/useCases/GetDatasetDownloadCount.ts b/src/datasets/domain/useCases/GetDatasetDownloadCount.ts new file mode 100644 index 00000000..59639bf6 --- /dev/null +++ b/src/datasets/domain/useCases/GetDatasetDownloadCount.ts @@ -0,0 +1,20 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { IDatasetsRepository } from '../repositories/IDatasetsRepository' +export class GetDatasetDownloadCount implements UseCase { + private datasetsRepository: IDatasetsRepository + + constructor(datasetsRepository: IDatasetsRepository) { + this.datasetsRepository = datasetsRepository + } + + /** + * Returns the Dataset Download Count. + * + * @param {number} [datasetId] - The dataset identifier. + * @param {boolean} [includeMDC(optional)] - Indicates whether to consider include counts from MDC start date or not. The default value is false + * @returns {Promise} + */ + async execute(datasetId: number, includeMDC?: boolean): Promise { + return await this.datasetsRepository.getDatasetDownloadCount(datasetId, includeMDC) + } +} diff --git a/src/datasets/index.ts b/src/datasets/index.ts index 3667cbf7..573edb11 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -17,6 +17,7 @@ import { PublishDataset } from './domain/useCases/PublishDataset' import { UpdateDataset } from './domain/useCases/UpdateDataset' import { GetDatasetVersionDiff } from './domain/useCases/GetDatasetVersionDiff' import { DeaccessionDataset } from './domain/useCases/DeaccessionDataset' +import { GetDatasetDownloadCount } from './domain/useCases/GetDatasetDownloadCount' const datasetsRepository = new DatasetsRepository() @@ -48,6 +49,7 @@ const updateDataset = new UpdateDataset( datasetResourceValidator ) const deaccessionDataset = new DeaccessionDataset(datasetsRepository) +const getDatasetDownloadCount = new GetDatasetDownloadCount(datasetsRepository) export { getDataset, @@ -62,7 +64,8 @@ export { publishDataset, createDataset, updateDataset, - deaccessionDataset + deaccessionDataset, + getDatasetDownloadCount } 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 2107d960..0b9fd8bb 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -235,4 +235,18 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi throw error }) } + + public async getDatasetDownloadCount(datasetId: number, includeMDC?: boolean): Promise { + const queryParams = includeMDC !== undefined ? { includeMDC } : {} + + return this.doGet( + this.buildApiEndpoint(this.datasetsResourceName, `${datasetId}/download/count`), + true, + queryParams + ) + .then((response) => response.data.downloadCount) + .catch((error) => { + throw error + }) + } } diff --git a/test/functional/datasets/GetDatasetDownloadCount.test.ts b/test/functional/datasets/GetDatasetDownloadCount.test.ts new file mode 100644 index 00000000..e7d51011 --- /dev/null +++ b/test/functional/datasets/GetDatasetDownloadCount.test.ts @@ -0,0 +1,98 @@ +import { + getDatasetDownloadCount, + createDataset, + publishDataset, + VersionUpdateType +} from '../../../src/datasets' +import { ApiConfig, ReadError } from '../../../src' +import { TestConstants } from '../../testHelpers/TestConstants' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { + deletePublishedDatasetViaApi, + deleteUnpublishedDatasetViaApi +} from '../../testHelpers/datasets/datasetHelper' + +const testDataset = { + license: { + name: 'CC0 1.0', + uri: 'http://creativecommons.org/publicdomain/zero/1.0', + iconUri: 'https://licensebuttons.net/p/zero/1.0/88x31.png' + }, + metadataBlockValues: [ + { + name: 'citation', + fields: { + title: 'Dataset for download count testing', + author: [ + { + authorName: 'Test, User', + authorAffiliation: 'Dataverse.org' + } + ], + datasetContact: [ + { + datasetContactEmail: 'testuser@mailinator.com', + datasetContactName: 'User, Test' + } + ], + dsDescription: [ + { + dsDescriptionValue: 'This dataset is used for testing the download count API.' + } + ], + subject: ['Computer Science'] + } + } + ] +} + +describe('execute', () => { + beforeEach(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + test('should return download count for a dataset', async () => { + const createdDatasetIdentifiers = await createDataset.execute(testDataset) + + await publishDataset.execute(createdDatasetIdentifiers.persistentId, VersionUpdateType.MAJOR) + + const downloadCount = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId) + + expect(downloadCount).toBe(0) + + await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId) + }) + + test('should return download count including MDC data', async () => { + const createdDatasetIdentifiers = await createDataset.execute(testDataset) + + await publishDataset.execute(createdDatasetIdentifiers.persistentId, VersionUpdateType.MAJOR) + + const downloadCount = await getDatasetDownloadCount.execute( + createdDatasetIdentifiers.numericId, + true + ) + + expect(downloadCount).toBe(0) + + await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId) + }) + + test('should throw an error when dataset ID is invalid', async () => { + await expect(getDatasetDownloadCount.execute(999999)).rejects.toBeInstanceOf(ReadError) + }) + + test('should return zero if dataset has no downloads', async () => { + const createdDatasetIdentifiers = await createDataset.execute(testDataset) + + const downloadCount = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId) + + expect(downloadCount).toBe(0) + + await deleteUnpublishedDatasetViaApi(createdDatasetIdentifiers.numericId) + }) +}) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 1f8add69..16cb0124 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -925,4 +925,41 @@ describe('DatasetsRepository', () => { ).rejects.toBeInstanceOf(WriteError) }) }) + + describe('getDatasetDownloadCount', () => { + test('should return download count for a dataset', async () => { + const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + const fileMetadata = { + description: 'test description', + directoryLabel: 'directoryLabel', + categories: ['category1', 'category2'] + } + await uploadFileViaApi(testDatasetIds.numericId, testTextFile1Name, fileMetadata) + await publishDatasetViaApi(testDatasetIds.numericId) + await waitForNoLocks(testDatasetIds.numericId, 10) + const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId) + + expect(actual).toBe(0) + + await deletePublishedDatasetViaApi(testDatasetIds.persistentId) + }) + + test('should return download count including MDC data', async () => { + const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + await publishDatasetViaApi(testDatasetIds.numericId) + await waitForNoLocks(testDatasetIds.numericId, 10) + + const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId, true) + + expect(actual).toBe(0) + + await deletePublishedDatasetViaApi(testDatasetIds.persistentId) + }) + + test('should return error when dataset does not exist', async () => { + await expect(sut.getDatasetDownloadCount(nonExistentTestDatasetId)).rejects.toBeInstanceOf( + ReadError + ) + }) + }) }) diff --git a/test/unit/datasets/GetDatasetDownloadCount.test.ts b/test/unit/datasets/GetDatasetDownloadCount.test.ts new file mode 100644 index 00000000..c5830f5c --- /dev/null +++ b/test/unit/datasets/GetDatasetDownloadCount.test.ts @@ -0,0 +1,30 @@ +import { GetDatasetDownloadCount } from '../../../src/datasets/domain/useCases/GetDatasetDownloadCount' +import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository' +import { ReadError } from '../../../src/core/domain/repositories/ReadError' + +describe('execute', () => { + const testDatasetId = 1 + const testCount = 10 + + test('should return count on repository success filtering by id', async () => { + const filesRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository + filesRepositoryStub.getDatasetDownloadCount = jest.fn().mockResolvedValue(testCount) + const sut = new GetDatasetDownloadCount(filesRepositoryStub) + + const actual = await sut.execute(testDatasetId) + + expect(actual).toBe(testCount) + expect(filesRepositoryStub.getDatasetDownloadCount).toHaveBeenCalledWith( + testDatasetId, + undefined + ) + }) + + test('should return error result on repository error', async () => { + const filesRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository + filesRepositoryStub.getDatasetDownloadCount = jest.fn().mockRejectedValue(new ReadError()) + const sut = new GetDatasetDownloadCount(filesRepositoryStub) + + await expect(sut.execute(testDatasetId)).rejects.toThrow(ReadError) + }) +}) From f31af9c8f9908f4b4e0b5dd4f9b0cbfc65b8cf5a Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 5 Mar 2025 12:10:33 -0500 Subject: [PATCH 2/9] Revert "feat: use case for dataset download count" This reverts commit 20f759c332e7e966f12d97f4f3bd1d6400ecc609. --- docs/useCases.md | 23 ----- .../repositories/IDatasetsRepository.ts | 1 - .../useCases/GetDatasetDownloadCount.ts | 20 ---- src/datasets/index.ts | 5 +- .../infra/repositories/DatasetsRepository.ts | 14 --- .../datasets/GetDatasetDownloadCount.test.ts | 98 ------------------- .../datasets/DatasetsRepository.test.ts | 37 ------- .../datasets/GetDatasetDownloadCount.test.ts | 30 ------ 8 files changed, 1 insertion(+), 227 deletions(-) delete mode 100644 src/datasets/domain/useCases/GetDatasetDownloadCount.ts delete mode 100644 test/functional/datasets/GetDatasetDownloadCount.test.ts delete mode 100644 test/unit/datasets/GetDatasetDownloadCount.test.ts diff --git a/docs/useCases.md b/docs/useCases.md index 76e7bfe7..47e09f6c 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -807,29 +807,6 @@ The `version` parameter should be a string or a [DatasetNotNumberedVersion](../s You cannot deaccession a dataset more than once. If you call this endpoint twice for the same dataset version, you will get a not found error on the second call, since the dataset you are looking for will no longer be published since it is already deaccessioned. -#### Get Download Count of a Dataset - -Total number of downloads requested for a dataset, given a dataset numeric identifier, - -##### Example call: - -```typescript -import { getDatasetDownloadCount } from '@iqss/dataverse-client-javascript' - -/* ... */ - -const datasetId = 1 -const includeMDC = true - -getDatasetDownloadCount.execute(datasetId, includeMDC) - -/* ... */ -``` - -_See [use case](../src/datasets/domain/useCases/GetDatasetDownloadCount.ts) implementation_. -The `datasetId` parameter is a number for numeric identifiers. -The `includeMDC` parameter is optional. If MDC is enabled the count will be limited to the time before MDC start if the optional `includeMDC` parameter is not included or set to False. Setting `includeMDC` to True will ignore the `:MDCStartDate` setting and return a total count. - ## Files ### Files read use cases diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 9d035a59..dd2b954d 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -51,5 +51,4 @@ export interface IDatasetsRepository { datasetVersionId: string, deaccessionDTO: DatasetDeaccessionDTO ): Promise - getDatasetDownloadCount(datasetId: number, includeMDC?: boolean): Promise } diff --git a/src/datasets/domain/useCases/GetDatasetDownloadCount.ts b/src/datasets/domain/useCases/GetDatasetDownloadCount.ts deleted file mode 100644 index 59639bf6..00000000 --- a/src/datasets/domain/useCases/GetDatasetDownloadCount.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { UseCase } from '../../../core/domain/useCases/UseCase' -import { IDatasetsRepository } from '../repositories/IDatasetsRepository' -export class GetDatasetDownloadCount implements UseCase { - private datasetsRepository: IDatasetsRepository - - constructor(datasetsRepository: IDatasetsRepository) { - this.datasetsRepository = datasetsRepository - } - - /** - * Returns the Dataset Download Count. - * - * @param {number} [datasetId] - The dataset identifier. - * @param {boolean} [includeMDC(optional)] - Indicates whether to consider include counts from MDC start date or not. The default value is false - * @returns {Promise} - */ - async execute(datasetId: number, includeMDC?: boolean): Promise { - return await this.datasetsRepository.getDatasetDownloadCount(datasetId, includeMDC) - } -} diff --git a/src/datasets/index.ts b/src/datasets/index.ts index 573edb11..3667cbf7 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -17,7 +17,6 @@ import { PublishDataset } from './domain/useCases/PublishDataset' import { UpdateDataset } from './domain/useCases/UpdateDataset' import { GetDatasetVersionDiff } from './domain/useCases/GetDatasetVersionDiff' import { DeaccessionDataset } from './domain/useCases/DeaccessionDataset' -import { GetDatasetDownloadCount } from './domain/useCases/GetDatasetDownloadCount' const datasetsRepository = new DatasetsRepository() @@ -49,7 +48,6 @@ const updateDataset = new UpdateDataset( datasetResourceValidator ) const deaccessionDataset = new DeaccessionDataset(datasetsRepository) -const getDatasetDownloadCount = new GetDatasetDownloadCount(datasetsRepository) export { getDataset, @@ -64,8 +62,7 @@ export { publishDataset, createDataset, updateDataset, - deaccessionDataset, - getDatasetDownloadCount + deaccessionDataset } 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 0b9fd8bb..2107d960 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -235,18 +235,4 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi throw error }) } - - public async getDatasetDownloadCount(datasetId: number, includeMDC?: boolean): Promise { - const queryParams = includeMDC !== undefined ? { includeMDC } : {} - - return this.doGet( - this.buildApiEndpoint(this.datasetsResourceName, `${datasetId}/download/count`), - true, - queryParams - ) - .then((response) => response.data.downloadCount) - .catch((error) => { - throw error - }) - } } diff --git a/test/functional/datasets/GetDatasetDownloadCount.test.ts b/test/functional/datasets/GetDatasetDownloadCount.test.ts deleted file mode 100644 index e7d51011..00000000 --- a/test/functional/datasets/GetDatasetDownloadCount.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { - getDatasetDownloadCount, - createDataset, - publishDataset, - VersionUpdateType -} from '../../../src/datasets' -import { ApiConfig, ReadError } from '../../../src' -import { TestConstants } from '../../testHelpers/TestConstants' -import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' -import { - deletePublishedDatasetViaApi, - deleteUnpublishedDatasetViaApi -} from '../../testHelpers/datasets/datasetHelper' - -const testDataset = { - license: { - name: 'CC0 1.0', - uri: 'http://creativecommons.org/publicdomain/zero/1.0', - iconUri: 'https://licensebuttons.net/p/zero/1.0/88x31.png' - }, - metadataBlockValues: [ - { - name: 'citation', - fields: { - title: 'Dataset for download count testing', - author: [ - { - authorName: 'Test, User', - authorAffiliation: 'Dataverse.org' - } - ], - datasetContact: [ - { - datasetContactEmail: 'testuser@mailinator.com', - datasetContactName: 'User, Test' - } - ], - dsDescription: [ - { - dsDescriptionValue: 'This dataset is used for testing the download count API.' - } - ], - subject: ['Computer Science'] - } - } - ] -} - -describe('execute', () => { - beforeEach(async () => { - ApiConfig.init( - TestConstants.TEST_API_URL, - DataverseApiAuthMechanism.API_KEY, - process.env.TEST_API_KEY - ) - }) - - test('should return download count for a dataset', async () => { - const createdDatasetIdentifiers = await createDataset.execute(testDataset) - - await publishDataset.execute(createdDatasetIdentifiers.persistentId, VersionUpdateType.MAJOR) - - const downloadCount = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId) - - expect(downloadCount).toBe(0) - - await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId) - }) - - test('should return download count including MDC data', async () => { - const createdDatasetIdentifiers = await createDataset.execute(testDataset) - - await publishDataset.execute(createdDatasetIdentifiers.persistentId, VersionUpdateType.MAJOR) - - const downloadCount = await getDatasetDownloadCount.execute( - createdDatasetIdentifiers.numericId, - true - ) - - expect(downloadCount).toBe(0) - - await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId) - }) - - test('should throw an error when dataset ID is invalid', async () => { - await expect(getDatasetDownloadCount.execute(999999)).rejects.toBeInstanceOf(ReadError) - }) - - test('should return zero if dataset has no downloads', async () => { - const createdDatasetIdentifiers = await createDataset.execute(testDataset) - - const downloadCount = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId) - - expect(downloadCount).toBe(0) - - await deleteUnpublishedDatasetViaApi(createdDatasetIdentifiers.numericId) - }) -}) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 16cb0124..1f8add69 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -925,41 +925,4 @@ describe('DatasetsRepository', () => { ).rejects.toBeInstanceOf(WriteError) }) }) - - describe('getDatasetDownloadCount', () => { - test('should return download count for a dataset', async () => { - const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) - const fileMetadata = { - description: 'test description', - directoryLabel: 'directoryLabel', - categories: ['category1', 'category2'] - } - await uploadFileViaApi(testDatasetIds.numericId, testTextFile1Name, fileMetadata) - await publishDatasetViaApi(testDatasetIds.numericId) - await waitForNoLocks(testDatasetIds.numericId, 10) - const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId) - - expect(actual).toBe(0) - - await deletePublishedDatasetViaApi(testDatasetIds.persistentId) - }) - - test('should return download count including MDC data', async () => { - const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) - await publishDatasetViaApi(testDatasetIds.numericId) - await waitForNoLocks(testDatasetIds.numericId, 10) - - const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId, true) - - expect(actual).toBe(0) - - await deletePublishedDatasetViaApi(testDatasetIds.persistentId) - }) - - test('should return error when dataset does not exist', async () => { - await expect(sut.getDatasetDownloadCount(nonExistentTestDatasetId)).rejects.toBeInstanceOf( - ReadError - ) - }) - }) }) diff --git a/test/unit/datasets/GetDatasetDownloadCount.test.ts b/test/unit/datasets/GetDatasetDownloadCount.test.ts deleted file mode 100644 index c5830f5c..00000000 --- a/test/unit/datasets/GetDatasetDownloadCount.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { GetDatasetDownloadCount } from '../../../src/datasets/domain/useCases/GetDatasetDownloadCount' -import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository' -import { ReadError } from '../../../src/core/domain/repositories/ReadError' - -describe('execute', () => { - const testDatasetId = 1 - const testCount = 10 - - test('should return count on repository success filtering by id', async () => { - const filesRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository - filesRepositoryStub.getDatasetDownloadCount = jest.fn().mockResolvedValue(testCount) - const sut = new GetDatasetDownloadCount(filesRepositoryStub) - - const actual = await sut.execute(testDatasetId) - - expect(actual).toBe(testCount) - expect(filesRepositoryStub.getDatasetDownloadCount).toHaveBeenCalledWith( - testDatasetId, - undefined - ) - }) - - test('should return error result on repository error', async () => { - const filesRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository - filesRepositoryStub.getDatasetDownloadCount = jest.fn().mockRejectedValue(new ReadError()) - const sut = new GetDatasetDownloadCount(filesRepositoryStub) - - await expect(sut.execute(testDatasetId)).rejects.toThrow(ReadError) - }) -}) From 7e4475fb12bc785716d55aa3ef0de29a9be4b9b7 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 5 Mar 2025 12:37:18 -0500 Subject: [PATCH 3/9] Reapply "feat: use case for dataset download count" This reverts commit f31af9c8f9908f4b4e0b5dd4f9b0cbfc65b8cf5a. --- docs/useCases.md | 23 +++++ .../repositories/IDatasetsRepository.ts | 1 + .../useCases/GetDatasetDownloadCount.ts | 20 ++++ src/datasets/index.ts | 5 +- .../infra/repositories/DatasetsRepository.ts | 14 +++ .../datasets/GetDatasetDownloadCount.test.ts | 98 +++++++++++++++++++ .../datasets/DatasetsRepository.test.ts | 37 +++++++ .../datasets/GetDatasetDownloadCount.test.ts | 30 ++++++ 8 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 src/datasets/domain/useCases/GetDatasetDownloadCount.ts create mode 100644 test/functional/datasets/GetDatasetDownloadCount.test.ts create mode 100644 test/unit/datasets/GetDatasetDownloadCount.test.ts diff --git a/docs/useCases.md b/docs/useCases.md index 47e09f6c..76e7bfe7 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -807,6 +807,29 @@ The `version` parameter should be a string or a [DatasetNotNumberedVersion](../s You cannot deaccession a dataset more than once. If you call this endpoint twice for the same dataset version, you will get a not found error on the second call, since the dataset you are looking for will no longer be published since it is already deaccessioned. +#### Get Download Count of a Dataset + +Total number of downloads requested for a dataset, given a dataset numeric identifier, + +##### Example call: + +```typescript +import { getDatasetDownloadCount } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const datasetId = 1 +const includeMDC = true + +getDatasetDownloadCount.execute(datasetId, includeMDC) + +/* ... */ +``` + +_See [use case](../src/datasets/domain/useCases/GetDatasetDownloadCount.ts) implementation_. +The `datasetId` parameter is a number for numeric identifiers. +The `includeMDC` parameter is optional. If MDC is enabled the count will be limited to the time before MDC start if the optional `includeMDC` parameter is not included or set to False. Setting `includeMDC` to True will ignore the `:MDCStartDate` setting and return a total count. + ## Files ### Files read use cases diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index dd2b954d..9d035a59 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -51,4 +51,5 @@ export interface IDatasetsRepository { datasetVersionId: string, deaccessionDTO: DatasetDeaccessionDTO ): Promise + getDatasetDownloadCount(datasetId: number, includeMDC?: boolean): Promise } diff --git a/src/datasets/domain/useCases/GetDatasetDownloadCount.ts b/src/datasets/domain/useCases/GetDatasetDownloadCount.ts new file mode 100644 index 00000000..59639bf6 --- /dev/null +++ b/src/datasets/domain/useCases/GetDatasetDownloadCount.ts @@ -0,0 +1,20 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { IDatasetsRepository } from '../repositories/IDatasetsRepository' +export class GetDatasetDownloadCount implements UseCase { + private datasetsRepository: IDatasetsRepository + + constructor(datasetsRepository: IDatasetsRepository) { + this.datasetsRepository = datasetsRepository + } + + /** + * Returns the Dataset Download Count. + * + * @param {number} [datasetId] - The dataset identifier. + * @param {boolean} [includeMDC(optional)] - Indicates whether to consider include counts from MDC start date or not. The default value is false + * @returns {Promise} + */ + async execute(datasetId: number, includeMDC?: boolean): Promise { + return await this.datasetsRepository.getDatasetDownloadCount(datasetId, includeMDC) + } +} diff --git a/src/datasets/index.ts b/src/datasets/index.ts index 3667cbf7..573edb11 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -17,6 +17,7 @@ import { PublishDataset } from './domain/useCases/PublishDataset' import { UpdateDataset } from './domain/useCases/UpdateDataset' import { GetDatasetVersionDiff } from './domain/useCases/GetDatasetVersionDiff' import { DeaccessionDataset } from './domain/useCases/DeaccessionDataset' +import { GetDatasetDownloadCount } from './domain/useCases/GetDatasetDownloadCount' const datasetsRepository = new DatasetsRepository() @@ -48,6 +49,7 @@ const updateDataset = new UpdateDataset( datasetResourceValidator ) const deaccessionDataset = new DeaccessionDataset(datasetsRepository) +const getDatasetDownloadCount = new GetDatasetDownloadCount(datasetsRepository) export { getDataset, @@ -62,7 +64,8 @@ export { publishDataset, createDataset, updateDataset, - deaccessionDataset + deaccessionDataset, + getDatasetDownloadCount } 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 2107d960..0b9fd8bb 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -235,4 +235,18 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi throw error }) } + + public async getDatasetDownloadCount(datasetId: number, includeMDC?: boolean): Promise { + const queryParams = includeMDC !== undefined ? { includeMDC } : {} + + return this.doGet( + this.buildApiEndpoint(this.datasetsResourceName, `${datasetId}/download/count`), + true, + queryParams + ) + .then((response) => response.data.downloadCount) + .catch((error) => { + throw error + }) + } } diff --git a/test/functional/datasets/GetDatasetDownloadCount.test.ts b/test/functional/datasets/GetDatasetDownloadCount.test.ts new file mode 100644 index 00000000..e7d51011 --- /dev/null +++ b/test/functional/datasets/GetDatasetDownloadCount.test.ts @@ -0,0 +1,98 @@ +import { + getDatasetDownloadCount, + createDataset, + publishDataset, + VersionUpdateType +} from '../../../src/datasets' +import { ApiConfig, ReadError } from '../../../src' +import { TestConstants } from '../../testHelpers/TestConstants' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { + deletePublishedDatasetViaApi, + deleteUnpublishedDatasetViaApi +} from '../../testHelpers/datasets/datasetHelper' + +const testDataset = { + license: { + name: 'CC0 1.0', + uri: 'http://creativecommons.org/publicdomain/zero/1.0', + iconUri: 'https://licensebuttons.net/p/zero/1.0/88x31.png' + }, + metadataBlockValues: [ + { + name: 'citation', + fields: { + title: 'Dataset for download count testing', + author: [ + { + authorName: 'Test, User', + authorAffiliation: 'Dataverse.org' + } + ], + datasetContact: [ + { + datasetContactEmail: 'testuser@mailinator.com', + datasetContactName: 'User, Test' + } + ], + dsDescription: [ + { + dsDescriptionValue: 'This dataset is used for testing the download count API.' + } + ], + subject: ['Computer Science'] + } + } + ] +} + +describe('execute', () => { + beforeEach(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + test('should return download count for a dataset', async () => { + const createdDatasetIdentifiers = await createDataset.execute(testDataset) + + await publishDataset.execute(createdDatasetIdentifiers.persistentId, VersionUpdateType.MAJOR) + + const downloadCount = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId) + + expect(downloadCount).toBe(0) + + await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId) + }) + + test('should return download count including MDC data', async () => { + const createdDatasetIdentifiers = await createDataset.execute(testDataset) + + await publishDataset.execute(createdDatasetIdentifiers.persistentId, VersionUpdateType.MAJOR) + + const downloadCount = await getDatasetDownloadCount.execute( + createdDatasetIdentifiers.numericId, + true + ) + + expect(downloadCount).toBe(0) + + await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId) + }) + + test('should throw an error when dataset ID is invalid', async () => { + await expect(getDatasetDownloadCount.execute(999999)).rejects.toBeInstanceOf(ReadError) + }) + + test('should return zero if dataset has no downloads', async () => { + const createdDatasetIdentifiers = await createDataset.execute(testDataset) + + const downloadCount = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId) + + expect(downloadCount).toBe(0) + + await deleteUnpublishedDatasetViaApi(createdDatasetIdentifiers.numericId) + }) +}) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 1f8add69..16cb0124 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -925,4 +925,41 @@ describe('DatasetsRepository', () => { ).rejects.toBeInstanceOf(WriteError) }) }) + + describe('getDatasetDownloadCount', () => { + test('should return download count for a dataset', async () => { + const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + const fileMetadata = { + description: 'test description', + directoryLabel: 'directoryLabel', + categories: ['category1', 'category2'] + } + await uploadFileViaApi(testDatasetIds.numericId, testTextFile1Name, fileMetadata) + await publishDatasetViaApi(testDatasetIds.numericId) + await waitForNoLocks(testDatasetIds.numericId, 10) + const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId) + + expect(actual).toBe(0) + + await deletePublishedDatasetViaApi(testDatasetIds.persistentId) + }) + + test('should return download count including MDC data', async () => { + const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + await publishDatasetViaApi(testDatasetIds.numericId) + await waitForNoLocks(testDatasetIds.numericId, 10) + + const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId, true) + + expect(actual).toBe(0) + + await deletePublishedDatasetViaApi(testDatasetIds.persistentId) + }) + + test('should return error when dataset does not exist', async () => { + await expect(sut.getDatasetDownloadCount(nonExistentTestDatasetId)).rejects.toBeInstanceOf( + ReadError + ) + }) + }) }) diff --git a/test/unit/datasets/GetDatasetDownloadCount.test.ts b/test/unit/datasets/GetDatasetDownloadCount.test.ts new file mode 100644 index 00000000..c5830f5c --- /dev/null +++ b/test/unit/datasets/GetDatasetDownloadCount.test.ts @@ -0,0 +1,30 @@ +import { GetDatasetDownloadCount } from '../../../src/datasets/domain/useCases/GetDatasetDownloadCount' +import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository' +import { ReadError } from '../../../src/core/domain/repositories/ReadError' + +describe('execute', () => { + const testDatasetId = 1 + const testCount = 10 + + test('should return count on repository success filtering by id', async () => { + const filesRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository + filesRepositoryStub.getDatasetDownloadCount = jest.fn().mockResolvedValue(testCount) + const sut = new GetDatasetDownloadCount(filesRepositoryStub) + + const actual = await sut.execute(testDatasetId) + + expect(actual).toBe(testCount) + expect(filesRepositoryStub.getDatasetDownloadCount).toHaveBeenCalledWith( + testDatasetId, + undefined + ) + }) + + test('should return error result on repository error', async () => { + const filesRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository + filesRepositoryStub.getDatasetDownloadCount = jest.fn().mockRejectedValue(new ReadError()) + const sut = new GetDatasetDownloadCount(filesRepositoryStub) + + await expect(sut.execute(testDatasetId)).rejects.toThrow(ReadError) + }) +}) From 96b6e4bd3f7320163f0253cc83577ae443b71af2 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 5 Mar 2025 14:57:41 -0500 Subject: [PATCH 4/9] feat: update model --- docs/useCases.md | 15 +++++++-- .../domain/models/DatasetDownloadCount.ts | 5 +++ .../repositories/IDatasetsRepository.ts | 3 +- .../useCases/GetDatasetDownloadCount.ts | 10 +++--- .../infra/repositories/DatasetsRepository.ts | 6 +++- test/environment/.env | 2 +- .../datasets/GetDatasetDownloadCount.test.ts | 15 ++++----- .../datasets/DatasetsRepository.test.ts | 4 +-- test/unit/datasets/DatasetsRepository.test.ts | 33 +++++++++++++++++++ .../datasets/GetDatasetDownloadCount.test.ts | 13 ++++++-- 10 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 src/datasets/domain/models/DatasetDownloadCount.ts diff --git a/docs/useCases.md b/docs/useCases.md index 76e7bfe7..382f43ed 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -821,14 +821,25 @@ import { getDatasetDownloadCount } from '@iqss/dataverse-client-javascript' const datasetId = 1 const includeMDC = true -getDatasetDownloadCount.execute(datasetId, includeMDC) +getDatasetDownloadCount + .execute(datasetId, includeMDC) + .then((datasetDownloadCount: DatasetDownloadCount) => { + /* ... */ + }) /* ... */ ``` _See [use case](../src/datasets/domain/useCases/GetDatasetDownloadCount.ts) implementation_. + The `datasetId` parameter is a number for numeric identifiers. -The `includeMDC` parameter is optional. If MDC is enabled the count will be limited to the time before MDC start if the optional `includeMDC` parameter is not included or set to False. Setting `includeMDC` to True will ignore the `:MDCStartDate` setting and return a total count. +The `includeMDC` parameter is optional. + +Note: + +- Setting `includeMDC` to True will ignore the `MDCStartDate` setting and return a total count. +- 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` ## Files diff --git a/src/datasets/domain/models/DatasetDownloadCount.ts b/src/datasets/domain/models/DatasetDownloadCount.ts new file mode 100644 index 00000000..3a8b3ed7 --- /dev/null +++ b/src/datasets/domain/models/DatasetDownloadCount.ts @@ -0,0 +1,5 @@ +export interface DatasetDownloadCount { + id: number + downloadCount: number + MDCStartDate?: string +} diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 9d035a59..377a5df8 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -7,6 +7,7 @@ import { DatasetDTO } from '../dtos/DatasetDTO' import { DatasetDeaccessionDTO } from '../dtos/DatasetDeaccessionDTO' import { MetadataBlock } from '../../../metadataBlocks' import { DatasetVersionDiff } from '../models/DatasetVersionDiff' +import { DatasetDownloadCount } from '../models/DatasetDownloadCount' export interface IDatasetsRepository { getDataset( @@ -51,5 +52,5 @@ export interface IDatasetsRepository { datasetVersionId: string, deaccessionDTO: DatasetDeaccessionDTO ): Promise - getDatasetDownloadCount(datasetId: number, includeMDC?: boolean): Promise + getDatasetDownloadCount(datasetId: number, includeMDC?: boolean): Promise } diff --git a/src/datasets/domain/useCases/GetDatasetDownloadCount.ts b/src/datasets/domain/useCases/GetDatasetDownloadCount.ts index 59639bf6..84d9d6d5 100644 --- a/src/datasets/domain/useCases/GetDatasetDownloadCount.ts +++ b/src/datasets/domain/useCases/GetDatasetDownloadCount.ts @@ -1,6 +1,8 @@ import { UseCase } from '../../../core/domain/useCases/UseCase' +import { DatasetDownloadCount } from '../models/DatasetDownloadCount' import { IDatasetsRepository } from '../repositories/IDatasetsRepository' -export class GetDatasetDownloadCount implements UseCase { + +export class GetDatasetDownloadCount implements UseCase { private datasetsRepository: IDatasetsRepository constructor(datasetsRepository: IDatasetsRepository) { @@ -8,13 +10,13 @@ export class GetDatasetDownloadCount implements UseCase { } /** - * Returns the Dataset Download Count. + * Returns a DatasetDownloadCount instance, with dataset id, count and MDCStartDate(optional). * * @param {number} [datasetId] - The dataset identifier. * @param {boolean} [includeMDC(optional)] - Indicates whether to consider include counts from MDC start date or not. The default value is false - * @returns {Promise} + * @returns {Promise} */ - async execute(datasetId: number, includeMDC?: boolean): Promise { + async execute(datasetId: number, includeMDC?: boolean): Promise { return await this.datasetsRepository.getDatasetDownloadCount(datasetId, includeMDC) } } diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 0b9fd8bb..e27be34e 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -18,6 +18,7 @@ import { transformDatasetLocksResponseToDatasetLocks } from './transformers/data import { transformDatasetPreviewsResponseToDatasetPreviewSubset } from './transformers/datasetPreviewsTransformers' import { DatasetVersionDiff } from '../../domain/models/DatasetVersionDiff' import { transformDatasetVersionDiffResponseToDatasetVersionDiff } from './transformers/datasetVersionDiffTransformers' +import { DatasetDownloadCount } from '../../domain/models/DatasetDownloadCount' export interface GetAllDatasetPreviewsQueryParams { per_page?: number @@ -236,7 +237,10 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi }) } - public async getDatasetDownloadCount(datasetId: number, includeMDC?: boolean): Promise { + public async getDatasetDownloadCount( + datasetId: number, + includeMDC?: boolean + ): Promise { const queryParams = includeMDC !== undefined ? { includeMDC } : {} return this.doGet( diff --git a/test/environment/.env b/test/environment/.env index 80e9a14e..0c691d9b 100644 --- a/test/environment/.env +++ b/test/environment/.env @@ -1,6 +1,6 @@ POSTGRES_VERSION=13 DATAVERSE_DB_USER=dataverse -SOLR_VERSION=9.3.0 +SOLR_VERSION=9.8.0 DATAVERSE_IMAGE_REGISTRY=docker.io DATAVERSE_IMAGE_TAG=unstable DATAVERSE_BOOTSTRAP_TIMEOUT=5m diff --git a/test/functional/datasets/GetDatasetDownloadCount.test.ts b/test/functional/datasets/GetDatasetDownloadCount.test.ts index e7d51011..0179da4e 100644 --- a/test/functional/datasets/GetDatasetDownloadCount.test.ts +++ b/test/functional/datasets/GetDatasetDownloadCount.test.ts @@ -60,9 +60,9 @@ describe('execute', () => { await publishDataset.execute(createdDatasetIdentifiers.persistentId, VersionUpdateType.MAJOR) - const downloadCount = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId) + const actual = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId) - expect(downloadCount).toBe(0) + expect(actual.downloadCount).toBe(0) await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId) }) @@ -72,12 +72,9 @@ describe('execute', () => { await publishDataset.execute(createdDatasetIdentifiers.persistentId, VersionUpdateType.MAJOR) - const downloadCount = await getDatasetDownloadCount.execute( - createdDatasetIdentifiers.numericId, - true - ) + const actual = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId, true) - expect(downloadCount).toBe(0) + expect(actual.downloadCount).toBe(0) await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId) }) @@ -89,9 +86,9 @@ describe('execute', () => { test('should return zero if dataset has no downloads', async () => { const createdDatasetIdentifiers = await createDataset.execute(testDataset) - const downloadCount = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId) + const actual = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId) - expect(downloadCount).toBe(0) + expect(actual.downloadCount).toBe(0) await deleteUnpublishedDatasetViaApi(createdDatasetIdentifiers.numericId) }) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 16cb0124..cc347e41 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -939,7 +939,7 @@ describe('DatasetsRepository', () => { await waitForNoLocks(testDatasetIds.numericId, 10) const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId) - expect(actual).toBe(0) + expect(actual.downloadCount).toBe(0) await deletePublishedDatasetViaApi(testDatasetIds.persistentId) }) @@ -951,7 +951,7 @@ describe('DatasetsRepository', () => { const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId, true) - expect(actual).toBe(0) + expect(actual.downloadCount).toBe(0) await deletePublishedDatasetViaApi(testDatasetIds.persistentId) }) diff --git a/test/unit/datasets/DatasetsRepository.test.ts b/test/unit/datasets/DatasetsRepository.test.ts index 85ff2f44..9dfb2a4e 100644 --- a/test/unit/datasets/DatasetsRepository.test.ts +++ b/test/unit/datasets/DatasetsRepository.test.ts @@ -30,6 +30,7 @@ import { } from '../../testHelpers/datasets/datasetHelper' import { WriteError } from '../../../src' import { VersionUpdateType } from '../../../src/datasets/domain/models/Dataset' +import { DatasetDownloadCount } from '../../../src/datasets/domain/models/DatasetDownloadCount' describe('DatasetsRepository', () => { const sut: DatasetsRepository = new DatasetsRepository() @@ -1014,4 +1015,36 @@ describe('DatasetsRepository', () => { expect(error).toBeInstanceOf(Error) }) }) + + describe('getDatasetDownloaCount', () => { + const testDatasetDownloadCount: DatasetDownloadCount = { + id: testDatasetModel.id, + downloadCount: 1, + MDCStartDate: '2021-01-01' + } + test('should return citation when response is successful', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(testDatasetDownloadCount) + + const actual = await sut.getDatasetDownloadCount(testDatasetModel.id) + + expect(axios.get).toHaveBeenCalledWith( + `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/download/count`, + TestConstants.TEST_EXPECTED_UNAUTHENTICATED_REQUEST_CONFIG + ) + expect(actual).toStrictEqual(testDatasetDownloadCount) + }) + + test('should return error on repository read error', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) + + let error = undefined as unknown as ReadError + await sut.getDatasetDownloadCount(testDatasetModel.id).catch((e) => (error = e)) + + expect(axios.get).toHaveBeenCalledWith( + `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/download/count`, + TestConstants.TEST_EXPECTED_UNAUTHENTICATED_REQUEST_CONFIG + ) + expect(error).toBeInstanceOf(Error) + }) + }) }) diff --git a/test/unit/datasets/GetDatasetDownloadCount.test.ts b/test/unit/datasets/GetDatasetDownloadCount.test.ts index c5830f5c..f65b0082 100644 --- a/test/unit/datasets/GetDatasetDownloadCount.test.ts +++ b/test/unit/datasets/GetDatasetDownloadCount.test.ts @@ -1,19 +1,26 @@ import { GetDatasetDownloadCount } from '../../../src/datasets/domain/useCases/GetDatasetDownloadCount' import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository' import { ReadError } from '../../../src/core/domain/repositories/ReadError' +import { DatasetDownloadCount } from '../../../src/datasets/domain/models/DatasetDownloadCount' describe('execute', () => { const testDatasetId = 1 - const testCount = 10 + const testDatasetDownloadCount: DatasetDownloadCount = { + id: testDatasetId, + downloadCount: 1, + MDCStartDate: '2021-01-01' + } test('should return count on repository success filtering by id', async () => { const filesRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository - filesRepositoryStub.getDatasetDownloadCount = jest.fn().mockResolvedValue(testCount) + filesRepositoryStub.getDatasetDownloadCount = jest + .fn() + .mockResolvedValue(testDatasetDownloadCount) const sut = new GetDatasetDownloadCount(filesRepositoryStub) const actual = await sut.execute(testDatasetId) - expect(actual).toBe(testCount) + expect(actual).toBe(testDatasetDownloadCount) expect(filesRepositoryStub.getDatasetDownloadCount).toHaveBeenCalledWith( testDatasetId, undefined From d5b9b733657304231cbd38f6e8f0be81ce9185bb Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 5 Mar 2025 20:20:31 -0500 Subject: [PATCH 5/9] fix: some tests --- docs/useCases.md | 2 - .../infra/repositories/DatasetsRepository.ts | 2 +- .../datasets/DatasetsRepository.test.ts | 49 +++++++++---------- test/unit/datasets/DatasetsRepository.test.ts | 4 +- 4 files changed, 25 insertions(+), 32 deletions(-) diff --git a/docs/useCases.md b/docs/useCases.md index 02222df2..999751a6 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -862,8 +862,6 @@ _See [use case](../src/datasets/domain/useCases/GetDatasetDownloadCount.ts) impl The `datasetId` parameter is a number for numeric identifiers. The `includeMDC` parameter is optional. -Note: - - Setting `includeMDC` to True will ignore the `MDCStartDate` setting and return a total count. - 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` diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 861b2389..ab497b88 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -249,7 +249,7 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi true, queryParams ) - .then((response) => response.data.downloadCount) + .then((response) => response.data) .catch((error) => { throw error }) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 8bdb2880..b6798e78 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -1108,6 +1108,28 @@ describe('DatasetsRepository', () => { await deletePublishedDatasetViaApi(testDatasetIds.persistentId) }) + }) + + describe('getDatasetDownloadCount', () => { + test('should return download count for a dataset', async () => { + const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + await publishDatasetViaApi(testDatasetIds.numericId) + await waitForNoLocks(testDatasetIds.numericId, 10) + const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId) + + expect(actual.downloadCount).toBe(0) + }) + + test('should return download count including MDC data', async () => { + const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + await publishDatasetViaApi(testDatasetIds.numericId) + await waitForNoLocks(testDatasetIds.numericId, 10) + + const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId, true) + + expect(actual.downloadCount).toBe(0) + }) + test('should return error when dataset does not exist', async () => { await expect(sut.getDatasetDownloadCount(nonExistentTestDatasetId)).rejects.toBeInstanceOf( ReadError @@ -1121,32 +1143,5 @@ describe('DatasetsRepository', () => { expectedError ) }) - - describe('getDatasetDownloadCount', () => { - test('should return download count for a dataset', async () => { - const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) - const fileMetadata = { - description: 'test description', - directoryLabel: 'directoryLabel', - categories: ['category1', 'category2'] - } - await uploadFileViaApi(testDatasetIds.numericId, testTextFile1Name, fileMetadata) - await publishDatasetViaApi(testDatasetIds.numericId) - await waitForNoLocks(testDatasetIds.numericId, 10) - const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId) - - expect(actual.downloadCount).toBe(0) - }) - - test('should return download count including MDC data', async () => { - const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) - await publishDatasetViaApi(testDatasetIds.numericId) - await waitForNoLocks(testDatasetIds.numericId, 10) - - const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId, true) - - expect(actual.downloadCount).toBe(0) - }) - }) }) }) diff --git a/test/unit/datasets/DatasetsRepository.test.ts b/test/unit/datasets/DatasetsRepository.test.ts index 78f36b23..fd738f36 100644 --- a/test/unit/datasets/DatasetsRepository.test.ts +++ b/test/unit/datasets/DatasetsRepository.test.ts @@ -1030,7 +1030,7 @@ describe('DatasetsRepository', () => { expect(axios.get).toHaveBeenCalledWith( `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/download/count`, - TestConstants.TEST_EXPECTED_UNAUTHENTICATED_REQUEST_CONFIG + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY ) expect(actual).toStrictEqual(testDatasetDownloadCount) }) @@ -1043,7 +1043,7 @@ describe('DatasetsRepository', () => { expect(axios.get).toHaveBeenCalledWith( `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/download/count`, - TestConstants.TEST_EXPECTED_UNAUTHENTICATED_REQUEST_CONFIG + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY ) expect(error).toBeInstanceOf(Error) }) From d18f9492720b1f4885bd217f1874739299db89ef Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 7 Mar 2025 10:36:34 -0500 Subject: [PATCH 6/9] feat: update testcase --- .../datasets/GetDatasetDownloadCount.test.ts | 95 ------------------- test/unit/datasets/DatasetsRepository.test.ts | 2 +- 2 files changed, 1 insertion(+), 96 deletions(-) delete mode 100644 test/functional/datasets/GetDatasetDownloadCount.test.ts diff --git a/test/functional/datasets/GetDatasetDownloadCount.test.ts b/test/functional/datasets/GetDatasetDownloadCount.test.ts deleted file mode 100644 index 0179da4e..00000000 --- a/test/functional/datasets/GetDatasetDownloadCount.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { - getDatasetDownloadCount, - createDataset, - publishDataset, - VersionUpdateType -} from '../../../src/datasets' -import { ApiConfig, ReadError } from '../../../src' -import { TestConstants } from '../../testHelpers/TestConstants' -import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' -import { - deletePublishedDatasetViaApi, - deleteUnpublishedDatasetViaApi -} from '../../testHelpers/datasets/datasetHelper' - -const testDataset = { - license: { - name: 'CC0 1.0', - uri: 'http://creativecommons.org/publicdomain/zero/1.0', - iconUri: 'https://licensebuttons.net/p/zero/1.0/88x31.png' - }, - metadataBlockValues: [ - { - name: 'citation', - fields: { - title: 'Dataset for download count testing', - author: [ - { - authorName: 'Test, User', - authorAffiliation: 'Dataverse.org' - } - ], - datasetContact: [ - { - datasetContactEmail: 'testuser@mailinator.com', - datasetContactName: 'User, Test' - } - ], - dsDescription: [ - { - dsDescriptionValue: 'This dataset is used for testing the download count API.' - } - ], - subject: ['Computer Science'] - } - } - ] -} - -describe('execute', () => { - beforeEach(async () => { - ApiConfig.init( - TestConstants.TEST_API_URL, - DataverseApiAuthMechanism.API_KEY, - process.env.TEST_API_KEY - ) - }) - - test('should return download count for a dataset', async () => { - const createdDatasetIdentifiers = await createDataset.execute(testDataset) - - await publishDataset.execute(createdDatasetIdentifiers.persistentId, VersionUpdateType.MAJOR) - - const actual = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId) - - expect(actual.downloadCount).toBe(0) - - await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId) - }) - - test('should return download count including MDC data', async () => { - const createdDatasetIdentifiers = await createDataset.execute(testDataset) - - await publishDataset.execute(createdDatasetIdentifiers.persistentId, VersionUpdateType.MAJOR) - - const actual = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId, true) - - expect(actual.downloadCount).toBe(0) - - await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId) - }) - - test('should throw an error when dataset ID is invalid', async () => { - await expect(getDatasetDownloadCount.execute(999999)).rejects.toBeInstanceOf(ReadError) - }) - - test('should return zero if dataset has no downloads', async () => { - const createdDatasetIdentifiers = await createDataset.execute(testDataset) - - const actual = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId) - - expect(actual.downloadCount).toBe(0) - - await deleteUnpublishedDatasetViaApi(createdDatasetIdentifiers.numericId) - }) -}) diff --git a/test/unit/datasets/DatasetsRepository.test.ts b/test/unit/datasets/DatasetsRepository.test.ts index fd738f36..5a15d5c3 100644 --- a/test/unit/datasets/DatasetsRepository.test.ts +++ b/test/unit/datasets/DatasetsRepository.test.ts @@ -1024,7 +1024,7 @@ describe('DatasetsRepository', () => { MDCStartDate: '2021-01-01' } test('should return citation when response is successful', async () => { - jest.spyOn(axios, 'get').mockResolvedValue(testDatasetDownloadCount) + jest.spyOn(axios, 'get').mockResolvedValue({ data: testDatasetDownloadCount }) const actual = await sut.getDatasetDownloadCount(testDatasetModel.id) From df0bd82ac8bafbdfb1d264a46f3d68e33f397d14 Mon Sep 17 00:00:00 2001 From: Cheng Shi <91049239+ChengShi-1@users.noreply.github.com> Date: Wed, 19 Mar 2025 16:01:22 -0400 Subject: [PATCH 7/9] Update src/datasets/domain/useCases/GetDatasetDownloadCount.ts Co-authored-by: Ellen Kraffmiller --- src/datasets/domain/useCases/GetDatasetDownloadCount.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datasets/domain/useCases/GetDatasetDownloadCount.ts b/src/datasets/domain/useCases/GetDatasetDownloadCount.ts index 84d9d6d5..5020ed7a 100644 --- a/src/datasets/domain/useCases/GetDatasetDownloadCount.ts +++ b/src/datasets/domain/useCases/GetDatasetDownloadCount.ts @@ -16,7 +16,7 @@ export class GetDatasetDownloadCount implements UseCase { * @param {boolean} [includeMDC(optional)] - Indicates whether to consider include counts from MDC start date or not. The default value is false * @returns {Promise} */ - async execute(datasetId: number, includeMDC?: boolean): Promise { + async execute(datasetId: number | string, includeMDC?: boolean): Promise { return await this.datasetsRepository.getDatasetDownloadCount(datasetId, includeMDC) } } From fc6a700224fd0f724247da23715459a4d6a95f31 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 19 Mar 2025 16:58:52 -0400 Subject: [PATCH 8/9] fix: testcase and reset the form after submit --- docs/useCases.md | 2 +- src/datasets/domain/repositories/IDatasetsRepository.ts | 5 ++++- src/datasets/domain/useCases/GetDatasetDownloadCount.ts | 4 ++-- src/datasets/infra/repositories/DatasetsRepository.ts | 4 ++-- test/environment/docker-compose.yml | 2 +- test/integration/datasets/DatasetsRepository.test.ts | 4 +++- test/unit/datasets/DatasetsRepository.test.ts | 4 ++-- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/useCases.md b/docs/useCases.md index 999751a6..f899eb02 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -859,7 +859,7 @@ getDatasetDownloadCount _See [use case](../src/datasets/domain/useCases/GetDatasetDownloadCount.ts) implementation_. -The `datasetId` parameter is a number for numeric identifiers. +The `datasetId` parameter is a number for numeric identifiers or string for persistent identifiers. The `includeMDC` parameter is optional. - Setting `includeMDC` to True will ignore the `MDCStartDate` setting and return a total count. diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index b74acb4f..9b9c2105 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -53,6 +53,9 @@ export interface IDatasetsRepository { datasetVersionId: string, deaccessionDTO: DatasetDeaccessionDTO ): Promise - getDatasetDownloadCount(datasetId: number, includeMDC?: boolean): Promise + getDatasetDownloadCount( + datasetId: number | string, + includeMDC?: boolean + ): Promise getDatasetVersionsSummaries(datasetId: number | string): Promise } diff --git a/src/datasets/domain/useCases/GetDatasetDownloadCount.ts b/src/datasets/domain/useCases/GetDatasetDownloadCount.ts index 84d9d6d5..98086c29 100644 --- a/src/datasets/domain/useCases/GetDatasetDownloadCount.ts +++ b/src/datasets/domain/useCases/GetDatasetDownloadCount.ts @@ -12,11 +12,11 @@ export class GetDatasetDownloadCount implements UseCase { /** * Returns a DatasetDownloadCount instance, with dataset id, count and MDCStartDate(optional). * - * @param {number} [datasetId] - The dataset identifier. + * @param {number | string} [datasetId] - The dataset identifier. * @param {boolean} [includeMDC(optional)] - Indicates whether to consider include counts from MDC start date or not. The default value is false * @returns {Promise} */ - async execute(datasetId: number, includeMDC?: boolean): Promise { + async execute(datasetId: number | string, includeMDC?: boolean): Promise { return await this.datasetsRepository.getDatasetDownloadCount(datasetId, includeMDC) } } diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index ab497b88..edf87d36 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -239,13 +239,13 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi } public async getDatasetDownloadCount( - datasetId: number, + datasetId: number | string, includeMDC?: boolean ): Promise { const queryParams = includeMDC !== undefined ? { includeMDC } : {} return this.doGet( - this.buildApiEndpoint(this.datasetsResourceName, `${datasetId}/download/count`), + this.buildApiEndpoint(this.datasetsResourceName, `download/count`, datasetId), true, queryParams ) diff --git a/test/environment/docker-compose.yml b/test/environment/docker-compose.yml index ad98d16b..31d90dd1 100644 --- a/test/environment/docker-compose.yml +++ b/test/environment/docker-compose.yml @@ -32,7 +32,7 @@ services: -Ddataverse.files.localstack1.access-key=default -Ddataverse.files.localstack1.secret-key=default ports: - - '8080:8080' + - '8081:8080' networks: - dataverse depends_on: diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index b6798e78..2e750d0c 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -1106,7 +1106,9 @@ describe('DatasetsRepository', () => { expect(actual[1].versionNumber).toBe('1.0') expect(actual[1].summary).toBe(DatasetVersionSummaryStringValues.firstPublished) - await deletePublishedDatasetViaApi(testDatasetIds.persistentId) + // await deletePublishedDatasetViaApi(testDatasetIds.persistentId) + console.log('od', testDatasetIds.numericId) + console.log('X-Dataverse-Key', process.env.TEST_API_KEY) }) }) diff --git a/test/unit/datasets/DatasetsRepository.test.ts b/test/unit/datasets/DatasetsRepository.test.ts index 5a15d5c3..fe53ed49 100644 --- a/test/unit/datasets/DatasetsRepository.test.ts +++ b/test/unit/datasets/DatasetsRepository.test.ts @@ -1017,13 +1017,13 @@ describe('DatasetsRepository', () => { }) }) - describe('getDatasetDownloaCount', () => { + describe('getDatasetDownloadCount', () => { const testDatasetDownloadCount: DatasetDownloadCount = { id: testDatasetModel.id, downloadCount: 1, MDCStartDate: '2021-01-01' } - test('should return citation when response is successful', async () => { + test('should return download count when response is successful', async () => { jest.spyOn(axios, 'get').mockResolvedValue({ data: testDatasetDownloadCount }) const actual = await sut.getDatasetDownloadCount(testDatasetModel.id) From 8be896260483453361214745dc8f5c61fa7a6c52 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Thu, 20 Mar 2025 11:07:57 -0400 Subject: [PATCH 9/9] fix: dataset download count --- src/datasets/domain/models/DatasetDownloadCount.ts | 2 +- test/environment/docker-compose.yml | 2 +- .../datasets/DatasetsRepository.test.ts | 14 +++++++++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/datasets/domain/models/DatasetDownloadCount.ts b/src/datasets/domain/models/DatasetDownloadCount.ts index 3a8b3ed7..54300ffb 100644 --- a/src/datasets/domain/models/DatasetDownloadCount.ts +++ b/src/datasets/domain/models/DatasetDownloadCount.ts @@ -1,5 +1,5 @@ export interface DatasetDownloadCount { - id: number + id: number | string downloadCount: number MDCStartDate?: string } diff --git a/test/environment/docker-compose.yml b/test/environment/docker-compose.yml index 31d90dd1..ad98d16b 100644 --- a/test/environment/docker-compose.yml +++ b/test/environment/docker-compose.yml @@ -32,7 +32,7 @@ services: -Ddataverse.files.localstack1.access-key=default -Ddataverse.files.localstack1.secret-key=default ports: - - '8081:8080' + - '8080:8080' networks: - dataverse depends_on: diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 2e750d0c..2c28ac28 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -1106,9 +1106,7 @@ describe('DatasetsRepository', () => { expect(actual[1].versionNumber).toBe('1.0') expect(actual[1].summary).toBe(DatasetVersionSummaryStringValues.firstPublished) - // await deletePublishedDatasetViaApi(testDatasetIds.persistentId) - console.log('od', testDatasetIds.numericId) - console.log('X-Dataverse-Key', process.env.TEST_API_KEY) + await deletePublishedDatasetViaApi(testDatasetIds.persistentId) }) }) @@ -1132,6 +1130,16 @@ describe('DatasetsRepository', () => { expect(actual.downloadCount).toBe(0) }) + test('should return download count including MDC data with persistent ID', async () => { + const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + await publishDatasetViaApi(testDatasetIds.numericId) + await waitForNoLocks(testDatasetIds.numericId, 10) + + const actual = await sut.getDatasetDownloadCount(testDatasetIds.persistentId, true) + + expect(actual.downloadCount).toBe(0) + }) + test('should return error when dataset does not exist', async () => { await expect(sut.getDatasetDownloadCount(nonExistentTestDatasetId)).rejects.toBeInstanceOf( ReadError