Skip to content
Merged
32 changes: 32 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/datasets/domain/models/DatasetDownloadCount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface DatasetDownloadCount {
id: number | string
downloadCount: number
MDCStartDate?: string
}
5 changes: 5 additions & 0 deletions src/datasets/domain/repositories/IDatasetsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -52,5 +53,9 @@ export interface IDatasetsRepository {
datasetVersionId: string,
deaccessionDTO: DatasetDeaccessionDTO
): Promise<void>
getDatasetDownloadCount(
datasetId: number | string,
includeMDC?: boolean
): Promise<DatasetDownloadCount>
getDatasetVersionsSummaries(datasetId: number | string): Promise<DatasetVersionSummaryInfo[]>
}
22 changes: 22 additions & 0 deletions src/datasets/domain/useCases/GetDatasetDownloadCount.ts
Original file line number Diff line number Diff line change
@@ -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<DatasetDownloadCount> {
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<DatasetDownloadCount>}
*/
async execute(datasetId: number | string, includeMDC?: boolean): Promise<DatasetDownloadCount> {
return await this.datasetsRepository.getDatasetDownloadCount(datasetId, includeMDC)
}
}
3 changes: 3 additions & 0 deletions src/datasets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand All @@ -65,6 +67,7 @@ export {
createDataset,
updateDataset,
deaccessionDataset,
getDatasetDownloadCount,
getDatasetVersionsSummaries
}
export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion'
Expand Down
18 changes: 18 additions & 0 deletions src/datasets/infra/repositories/DatasetsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -237,6 +238,23 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
})
}

public async getDatasetDownloadCount(
datasetId: number | string,
includeMDC?: boolean
): Promise<DatasetDownloadCount> {
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<DatasetVersionSummaryInfo[]> {
Expand Down
35 changes: 35 additions & 0 deletions test/integration/datasets/DatasetsRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.`
)
Expand Down
33 changes: 33 additions & 0 deletions test/unit/datasets/DatasetsRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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()

Expand Down
37 changes: 37 additions & 0 deletions test/unit/datasets/GetDatasetDownloadCount.test.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})