diff --git a/docs/useCases.md b/docs/useCases.md index 66312def..f899eb02 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -834,6 +834,38 @@ 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) + .then((datasetDownloadCount: DatasetDownloadCount) => { + /* ... */ + }) + +/* ... */ +``` + +_See [use case](../src/datasets/domain/useCases/GetDatasetDownloadCount.ts) implementation_. + +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. +- 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 ### Files read use cases diff --git a/src/datasets/domain/models/DatasetDownloadCount.ts b/src/datasets/domain/models/DatasetDownloadCount.ts new file mode 100644 index 00000000..54300ffb --- /dev/null +++ b/src/datasets/domain/models/DatasetDownloadCount.ts @@ -0,0 +1,5 @@ +export interface DatasetDownloadCount { + id: number | string + downloadCount: number + MDCStartDate?: string +} diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index a5052dc7..9b9c2105 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' import { DatasetVersionSummaryInfo } from '../models/DatasetVersionSummaryInfo' export interface IDatasetsRepository { @@ -52,5 +53,9 @@ export interface IDatasetsRepository { datasetVersionId: string, deaccessionDTO: DatasetDeaccessionDTO ): 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 new file mode 100644 index 00000000..98086c29 --- /dev/null +++ b/src/datasets/domain/useCases/GetDatasetDownloadCount.ts @@ -0,0 +1,22 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { DatasetDownloadCount } from '../models/DatasetDownloadCount' +import { IDatasetsRepository } from '../repositories/IDatasetsRepository' + +export class GetDatasetDownloadCount implements UseCase { + private datasetsRepository: IDatasetsRepository + + constructor(datasetsRepository: IDatasetsRepository) { + this.datasetsRepository = datasetsRepository + } + + /** + * Returns a DatasetDownloadCount instance, with dataset id, count and MDCStartDate(optional). + * + * @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 | string, includeMDC?: boolean): Promise { + return await this.datasetsRepository.getDatasetDownloadCount(datasetId, includeMDC) + } +} diff --git a/src/datasets/index.ts b/src/datasets/index.ts index 121af2f4..bd5592b4 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' import { GetDatasetVersionsSummaries } from './domain/useCases/GetDatasetVersionsSummaries' const datasetsRepository = new DatasetsRepository() @@ -49,6 +50,7 @@ const updateDataset = new UpdateDataset( datasetResourceValidator ) const deaccessionDataset = new DeaccessionDataset(datasetsRepository) +const getDatasetDownloadCount = new GetDatasetDownloadCount(datasetsRepository) const getDatasetVersionsSummaries = new GetDatasetVersionsSummaries(datasetsRepository) export { @@ -65,6 +67,7 @@ export { createDataset, updateDataset, deaccessionDataset, + getDatasetDownloadCount, getDatasetVersionsSummaries } export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion' diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 4cd6467e..edf87d36 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' import { DatasetVersionSummaryInfo } from '../../domain/models/DatasetVersionSummaryInfo' export interface GetAllDatasetPreviewsQueryParams { @@ -237,6 +238,23 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi }) } + public async getDatasetDownloadCount( + datasetId: number | string, + includeMDC?: boolean + ): Promise { + const queryParams = includeMDC !== undefined ? { includeMDC } : {} + + return this.doGet( + this.buildApiEndpoint(this.datasetsResourceName, `download/count`, datasetId), + true, + queryParams + ) + .then((response) => response.data) + .catch((error) => { + throw error + }) + } + public async getDatasetVersionsSummaries( datasetId: string | number ): Promise { diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index d3e34bde..2c28ac28 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -1108,8 +1108,43 @@ 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 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 + ) + const expectedError = new ReadError( `[404] Dataset with ID ${nonExistentTestDatasetId} not found.` ) diff --git a/test/unit/datasets/DatasetsRepository.test.ts b/test/unit/datasets/DatasetsRepository.test.ts index 0f60fc65..fe53ed49 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' import { createDatasetVersionSummaryModel } from '../../testHelpers/datasets/datasetVersionsSummariesHelper' describe('DatasetsRepository', () => { @@ -1016,6 +1017,38 @@ describe('DatasetsRepository', () => { }) }) + describe('getDatasetDownloadCount', () => { + const testDatasetDownloadCount: DatasetDownloadCount = { + id: testDatasetModel.id, + downloadCount: 1, + MDCStartDate: '2021-01-01' + } + test('should return download count when response is successful', async () => { + jest.spyOn(axios, 'get').mockResolvedValue({ data: testDatasetDownloadCount }) + + const actual = await sut.getDatasetDownloadCount(testDatasetModel.id) + + expect(axios.get).toHaveBeenCalledWith( + `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/download/count`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY + ) + 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_AUTHENTICATED_REQUEST_CONFIG_API_KEY + ) + expect(error).toBeInstanceOf(Error) + }) + }) + describe('getDatasetVersionSummaries', () => { const testDatasetVersionSummaries = createDatasetVersionSummaryModel() diff --git a/test/unit/datasets/GetDatasetDownloadCount.test.ts b/test/unit/datasets/GetDatasetDownloadCount.test.ts new file mode 100644 index 00000000..f65b0082 --- /dev/null +++ b/test/unit/datasets/GetDatasetDownloadCount.test.ts @@ -0,0 +1,37 @@ +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 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(testDatasetDownloadCount) + const sut = new GetDatasetDownloadCount(filesRepositoryStub) + + const actual = await sut.execute(testDatasetId) + + expect(actual).toBe(testDatasetDownloadCount) + 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) + }) +})