Skip to content

Commit 20f759c

Browse files
committed
feat: use case for dataset download count
1 parent 25520a1 commit 20f759c

File tree

8 files changed

+227
-1
lines changed

8 files changed

+227
-1
lines changed

docs/useCases.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,29 @@ The `version` parameter should be a string or a [DatasetNotNumberedVersion](../s
807807

808808
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.
809809

810+
#### Get Download Count of a Dataset
811+
812+
Total number of downloads requested for a dataset, given a dataset numeric identifier,
813+
814+
##### Example call:
815+
816+
```typescript
817+
import { getDatasetDownloadCount } from '@iqss/dataverse-client-javascript'
818+
819+
/* ... */
820+
821+
const datasetId = 1
822+
const includeMDC = true
823+
824+
getDatasetDownloadCount.execute(datasetId, includeMDC)
825+
826+
/* ... */
827+
```
828+
829+
_See [use case](../src/datasets/domain/useCases/GetDatasetDownloadCount.ts) implementation_.
830+
The `datasetId` parameter is a number for numeric identifiers.
831+
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.
832+
810833
## Files
811834

812835
### Files read use cases

src/datasets/domain/repositories/IDatasetsRepository.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,5 @@ export interface IDatasetsRepository {
5151
datasetVersionId: string,
5252
deaccessionDTO: DatasetDeaccessionDTO
5353
): Promise<void>
54+
getDatasetDownloadCount(datasetId: number, includeMDC?: boolean): Promise<number>
5455
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { IDatasetsRepository } from '../repositories/IDatasetsRepository'
3+
export class GetDatasetDownloadCount implements UseCase<number> {
4+
private datasetsRepository: IDatasetsRepository
5+
6+
constructor(datasetsRepository: IDatasetsRepository) {
7+
this.datasetsRepository = datasetsRepository
8+
}
9+
10+
/**
11+
* Returns the Dataset Download Count.
12+
*
13+
* @param {number} [datasetId] - The dataset identifier.
14+
* @param {boolean} [includeMDC(optional)] - Indicates whether to consider include counts from MDC start date or not. The default value is false
15+
* @returns {Promise<number>}
16+
*/
17+
async execute(datasetId: number, includeMDC?: boolean): Promise<number> {
18+
return await this.datasetsRepository.getDatasetDownloadCount(datasetId, includeMDC)
19+
}
20+
}

src/datasets/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { PublishDataset } from './domain/useCases/PublishDataset'
1717
import { UpdateDataset } from './domain/useCases/UpdateDataset'
1818
import { GetDatasetVersionDiff } from './domain/useCases/GetDatasetVersionDiff'
1919
import { DeaccessionDataset } from './domain/useCases/DeaccessionDataset'
20+
import { GetDatasetDownloadCount } from './domain/useCases/GetDatasetDownloadCount'
2021

2122
const datasetsRepository = new DatasetsRepository()
2223

@@ -48,6 +49,7 @@ const updateDataset = new UpdateDataset(
4849
datasetResourceValidator
4950
)
5051
const deaccessionDataset = new DeaccessionDataset(datasetsRepository)
52+
const getDatasetDownloadCount = new GetDatasetDownloadCount(datasetsRepository)
5153

5254
export {
5355
getDataset,
@@ -62,7 +64,8 @@ export {
6264
publishDataset,
6365
createDataset,
6466
updateDataset,
65-
deaccessionDataset
67+
deaccessionDataset,
68+
getDatasetDownloadCount
6669
}
6770
export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion'
6871
export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions'

src/datasets/infra/repositories/DatasetsRepository.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,18 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
235235
throw error
236236
})
237237
}
238+
239+
public async getDatasetDownloadCount(datasetId: number, includeMDC?: boolean): Promise<number> {
240+
const queryParams = includeMDC !== undefined ? { includeMDC } : {}
241+
242+
return this.doGet(
243+
this.buildApiEndpoint(this.datasetsResourceName, `${datasetId}/download/count`),
244+
true,
245+
queryParams
246+
)
247+
.then((response) => response.data.downloadCount)
248+
.catch((error) => {
249+
throw error
250+
})
251+
}
238252
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import {
2+
getDatasetDownloadCount,
3+
createDataset,
4+
publishDataset,
5+
VersionUpdateType
6+
} from '../../../src/datasets'
7+
import { ApiConfig, ReadError } from '../../../src'
8+
import { TestConstants } from '../../testHelpers/TestConstants'
9+
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
10+
import {
11+
deletePublishedDatasetViaApi,
12+
deleteUnpublishedDatasetViaApi
13+
} from '../../testHelpers/datasets/datasetHelper'
14+
15+
const testDataset = {
16+
license: {
17+
name: 'CC0 1.0',
18+
uri: 'http://creativecommons.org/publicdomain/zero/1.0',
19+
iconUri: 'https://licensebuttons.net/p/zero/1.0/88x31.png'
20+
},
21+
metadataBlockValues: [
22+
{
23+
name: 'citation',
24+
fields: {
25+
title: 'Dataset for download count testing',
26+
author: [
27+
{
28+
authorName: 'Test, User',
29+
authorAffiliation: 'Dataverse.org'
30+
}
31+
],
32+
datasetContact: [
33+
{
34+
datasetContactEmail: '[email protected]',
35+
datasetContactName: 'User, Test'
36+
}
37+
],
38+
dsDescription: [
39+
{
40+
dsDescriptionValue: 'This dataset is used for testing the download count API.'
41+
}
42+
],
43+
subject: ['Computer Science']
44+
}
45+
}
46+
]
47+
}
48+
49+
describe('execute', () => {
50+
beforeEach(async () => {
51+
ApiConfig.init(
52+
TestConstants.TEST_API_URL,
53+
DataverseApiAuthMechanism.API_KEY,
54+
process.env.TEST_API_KEY
55+
)
56+
})
57+
58+
test('should return download count for a dataset', async () => {
59+
const createdDatasetIdentifiers = await createDataset.execute(testDataset)
60+
61+
await publishDataset.execute(createdDatasetIdentifiers.persistentId, VersionUpdateType.MAJOR)
62+
63+
const downloadCount = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId)
64+
65+
expect(downloadCount).toBe(0)
66+
67+
await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId)
68+
})
69+
70+
test('should return download count including MDC data', async () => {
71+
const createdDatasetIdentifiers = await createDataset.execute(testDataset)
72+
73+
await publishDataset.execute(createdDatasetIdentifiers.persistentId, VersionUpdateType.MAJOR)
74+
75+
const downloadCount = await getDatasetDownloadCount.execute(
76+
createdDatasetIdentifiers.numericId,
77+
true
78+
)
79+
80+
expect(downloadCount).toBe(0)
81+
82+
await deletePublishedDatasetViaApi(createdDatasetIdentifiers.persistentId)
83+
})
84+
85+
test('should throw an error when dataset ID is invalid', async () => {
86+
await expect(getDatasetDownloadCount.execute(999999)).rejects.toBeInstanceOf(ReadError)
87+
})
88+
89+
test('should return zero if dataset has no downloads', async () => {
90+
const createdDatasetIdentifiers = await createDataset.execute(testDataset)
91+
92+
const downloadCount = await getDatasetDownloadCount.execute(createdDatasetIdentifiers.numericId)
93+
94+
expect(downloadCount).toBe(0)
95+
96+
await deleteUnpublishedDatasetViaApi(createdDatasetIdentifiers.numericId)
97+
})
98+
})

test/integration/datasets/DatasetsRepository.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,4 +925,41 @@ describe('DatasetsRepository', () => {
925925
).rejects.toBeInstanceOf(WriteError)
926926
})
927927
})
928+
929+
describe('getDatasetDownloadCount', () => {
930+
test('should return download count for a dataset', async () => {
931+
const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
932+
const fileMetadata = {
933+
description: 'test description',
934+
directoryLabel: 'directoryLabel',
935+
categories: ['category1', 'category2']
936+
}
937+
await uploadFileViaApi(testDatasetIds.numericId, testTextFile1Name, fileMetadata)
938+
await publishDatasetViaApi(testDatasetIds.numericId)
939+
await waitForNoLocks(testDatasetIds.numericId, 10)
940+
const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId)
941+
942+
expect(actual).toBe(0)
943+
944+
await deletePublishedDatasetViaApi(testDatasetIds.persistentId)
945+
})
946+
947+
test('should return download count including MDC data', async () => {
948+
const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
949+
await publishDatasetViaApi(testDatasetIds.numericId)
950+
await waitForNoLocks(testDatasetIds.numericId, 10)
951+
952+
const actual = await sut.getDatasetDownloadCount(testDatasetIds.numericId, true)
953+
954+
expect(actual).toBe(0)
955+
956+
await deletePublishedDatasetViaApi(testDatasetIds.persistentId)
957+
})
958+
959+
test('should return error when dataset does not exist', async () => {
960+
await expect(sut.getDatasetDownloadCount(nonExistentTestDatasetId)).rejects.toBeInstanceOf(
961+
ReadError
962+
)
963+
})
964+
})
928965
})
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { GetDatasetDownloadCount } from '../../../src/datasets/domain/useCases/GetDatasetDownloadCount'
2+
import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository'
3+
import { ReadError } from '../../../src/core/domain/repositories/ReadError'
4+
5+
describe('execute', () => {
6+
const testDatasetId = 1
7+
const testCount = 10
8+
9+
test('should return count on repository success filtering by id', async () => {
10+
const filesRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository
11+
filesRepositoryStub.getDatasetDownloadCount = jest.fn().mockResolvedValue(testCount)
12+
const sut = new GetDatasetDownloadCount(filesRepositoryStub)
13+
14+
const actual = await sut.execute(testDatasetId)
15+
16+
expect(actual).toBe(testCount)
17+
expect(filesRepositoryStub.getDatasetDownloadCount).toHaveBeenCalledWith(
18+
testDatasetId,
19+
undefined
20+
)
21+
})
22+
23+
test('should return error result on repository error', async () => {
24+
const filesRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository
25+
filesRepositoryStub.getDatasetDownloadCount = jest.fn().mockRejectedValue(new ReadError())
26+
const sut = new GetDatasetDownloadCount(filesRepositoryStub)
27+
28+
await expect(sut.execute(testDatasetId)).rejects.toThrow(ReadError)
29+
})
30+
})

0 commit comments

Comments
 (0)