Skip to content

Commit f48695f

Browse files
committed
feat: use case of dataset uploadlimit
1 parent 9c7e9d8 commit f48695f

10 files changed

Lines changed: 196 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel
1111
- Datasets: Added `updateDatasetLicense` use case and repository method to support Dataverse endpoint `PUT /datasets/{id}/license`, for updating dataset license or custom terms.
1212
- Datasets: Added `getDatasetStorageDriver` use case and repository method to support Dataverse endpoint `GET /datasets/{identifier}/storageDriver`, for retrieving dataset storage driver configuration with properties: name, type, label, directUpload, directDownload, and uploadOutOfBand.
1313
- Datasets: Added `updateDatasetLicense` use case and repository method to support Dataverse endpoint `PUT /datasets/{id}/license`, for updating dataset license or custom terms
14+
- Datasets: Added `getDatasetUploadLimits` use case and repository method to support Dataverse endpoint `GET /datasets/{id}/uploadlimits`, for retrieving remaining storage upload quotas, if present.
1415
- New Use Case: [Get Collections For Linking Use Case](./docs/useCases.md#get-collections-for-linking).
1516
- New Use Case: [Create a Template](./docs/useCases.md#create-a-template) under Templates.
1617
- New Use Case: [Get a Template](./docs/useCases.md#get-a-template) under Templates.

docs/useCases.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ The different use cases currently available in the package are classified below,
5050
- [Get Dataset Storage Driver](#get-dataset-storage-driver)
5151
- [Get Dataset Available Dataset Types](#get-dataset-available-dataset-types)
5252
- [Get Dataset Available Dataset Type](#get-dataset-available-dataset-type)
53+
- [Get Dataset Upload Limits](#get-dataset-upload-limits)
5354
- [Datasets write use cases](#datasets-write-use-cases)
5455
- [Create a Dataset](#create-a-dataset)
5556
- [Update a Dataset](#update-a-dataset)
@@ -1516,6 +1517,30 @@ deleteDatasetType.execute(datasetTypeId).then(() => {
15161517

15171518
_See [use case](../src/datasets/domain/useCases/DeleteDatasetType.ts) implementation_.
15181519

1520+
#### Get Dataset Upload Limits
1521+
1522+
Returns a [DatasetUploadLimits](../src/datasets/domain/models/DatasetUploadLimits.ts) instance with the remaining dataset storage and/or file upload quotas, if present.
1523+
1524+
##### Example call:
1525+
1526+
```typescript
1527+
import { getDatasetUploadLimits } from '@iqss/dataverse-client-javascript'
1528+
1529+
/* ... */
1530+
1531+
const datasetId = 'doi:10.77777/FK2/AAAAAA'
1532+
1533+
getDatasetUploadLimits.execute(datasetId).then((uploadLimits: DatasetUploadLimits) => {
1534+
/* ... */
1535+
})
1536+
1537+
/* ... */
1538+
```
1539+
1540+
_See [use case](../src/datasets/domain/useCases/GetDatasetUploadLimits.ts) implementation_.
1541+
1542+
If the backend does not define any quota limits for the dataset, the returned object can be empty (`{}`).
1543+
15191544
## Files
15201545

15211546
### Files read use cases
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface DatasetUploadLimits {
2+
numberOfFilesRemaining?: number
3+
storageQuotaRemaining?: number
4+
}

src/datasets/domain/repositories/IDatasetsRepository.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { TermsOfAccess } from '../models/Dataset'
1717
import { DatasetLicenseUpdateRequest } from '../dtos/DatasetLicenseUpdateRequest'
1818
import { DatasetTypeDTO } from '../dtos/DatasetTypeDTO'
1919
import { StorageDriver } from '../models/StorageDriver'
20+
import { DatasetUploadLimits } from '../models/DatasetUploadLimits'
2021

2122
export interface IDatasetsRepository {
2223
getDataset(
@@ -102,4 +103,5 @@ export interface IDatasetsRepository {
102103
payload: DatasetLicenseUpdateRequest
103104
): Promise<void>
104105
getDatasetStorageDriver(datasetId: number | string): Promise<StorageDriver>
106+
getDatasetUploadLimits(datasetId: number | string): Promise<DatasetUploadLimits>
105107
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { IDatasetsRepository } from '../repositories/IDatasetsRepository'
3+
import { DatasetUploadLimits } from '../models/DatasetUploadLimits'
4+
5+
export class GetDatasetUploadLimits implements UseCase<DatasetUploadLimits> {
6+
private datasetsRepository: IDatasetsRepository
7+
8+
constructor(datasetsRepository: IDatasetsRepository) {
9+
this.datasetsRepository = datasetsRepository
10+
}
11+
12+
/**
13+
* Returns the remaining dataset storage and/or file upload quotas (if present).
14+
*
15+
* @param {number | string} datasetId - The dataset identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers).
16+
* @returns {Promise<DatasetUploadLimits>}
17+
*/
18+
async execute(datasetId: number | string): Promise<DatasetUploadLimits> {
19+
return this.datasetsRepository.getDatasetUploadLimits(datasetId)
20+
}
21+
}

src/datasets/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { GetDatasetCitationInOtherFormats } from './domain/useCases/GetDatasetCi
3434
import { UpdateTermsOfAccess } from './domain/useCases/UpdateTermsOfAccess'
3535
import { UpdateDatasetLicense } from './domain/useCases/UpdateDatasetLicense'
3636
import { GetDatasetStorageDriver } from './domain/useCases/GetDatasetStorageDriver'
37+
import { GetDatasetUploadLimits } from './domain/useCases/GetDatasetUploadLimits'
3738

3839
const datasetsRepository = new DatasetsRepository()
3940

@@ -84,6 +85,7 @@ const getDatasetCitationInOtherFormats = new GetDatasetCitationInOtherFormats(da
8485
const updateTermsOfAccess = new UpdateTermsOfAccess(datasetsRepository)
8586
const updateDatasetLicense = new UpdateDatasetLicense(datasetsRepository)
8687
const getDatasetStorageDriver = new GetDatasetStorageDriver(datasetsRepository)
88+
const getDatasetUploadLimits = new GetDatasetUploadLimits(datasetsRepository)
8789

8890
export {
8991
getDataset,
@@ -115,7 +117,8 @@ export {
115117
setAvailableLicensesForDatasetType,
116118
deleteDatasetType,
117119
updateDatasetLicense,
118-
getDatasetStorageDriver
120+
getDatasetStorageDriver,
121+
getDatasetUploadLimits
119122
}
120123
export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion'
121124
export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions'
@@ -155,3 +158,4 @@ export { DatasetLinkedCollection } from './domain/models/DatasetLinkedCollection
155158
export { DatasetType } from './domain/models/DatasetType'
156159
export { DatasetTypeDTO } from './domain/dtos/DatasetTypeDTO'
157160
export { StorageDriver } from './domain/models/StorageDriver'
161+
export { DatasetUploadLimits } from './domain/models/DatasetUploadLimits'

src/datasets/infra/repositories/DatasetsRepository.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { transformTermsOfAccessToUpdatePayload } from './transformers/termsOfAcc
3030
import { DatasetLicenseUpdateRequest } from '../../domain/dtos/DatasetLicenseUpdateRequest'
3131
import { DatasetTypeDTO } from '../../domain/dtos/DatasetTypeDTO'
3232
import { StorageDriver } from '../../domain/models/StorageDriver'
33+
import { DatasetUploadLimits } from '../../domain/models/DatasetUploadLimits'
3334

3435
export interface GetAllDatasetPreviewsQueryParams {
3536
per_page?: number
@@ -511,4 +512,15 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi
511512
throw error
512513
})
513514
}
515+
516+
public async getDatasetUploadLimits(datasetId: number | string): Promise<DatasetUploadLimits> {
517+
return this.doGet(
518+
this.buildApiEndpoint(this.datasetsResourceName, 'uploadlimits', datasetId),
519+
true
520+
)
521+
.then((response) => (response.data?.data?.uploadLimits ?? {}) as DatasetUploadLimits)
522+
.catch((error) => {
523+
throw error
524+
})
525+
}
514526
}

test/integration/datasets/DatasetsRepository.test.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import {
88
waitForDatasetsIndexedInSolr,
99
deletePublishedDatasetViaApi,
1010
deaccessionDatasetViaApi,
11-
createDatasetLicenseModel
11+
createDatasetLicenseModel,
12+
setDatasetStorageSizeViaApi,
13+
setUseStorageQuotasViaApi
1214
} from '../../testHelpers/datasets/datasetHelper'
1315
import { ReadError } from '../../../src/core/domain/repositories/ReadError'
1416
import {
@@ -2229,4 +2231,45 @@ describe('DatasetsRepository', () => {
22292231
expect(typeof storageDriver.uploadOutOfBand).toBe('boolean')
22302232
})
22312233
})
2234+
2235+
describe('getDatasetUploadLimits', () => {
2236+
const testCollectionAlias = 'UploadLimitsQuotaDataset'
2237+
let testDatasetIds: CreatedDatasetIdentifiers
2238+
const testCollectionStorageQuotaInBytes = 1000
2239+
2240+
beforeAll(async () => {
2241+
await createCollectionViaApi(testCollectionAlias)
2242+
await publishCollectionViaApi(testCollectionAlias)
2243+
testDatasetIds = await createDataset.execute(
2244+
TestConstants.TEST_NEW_DATASET_DTO,
2245+
testCollectionAlias
2246+
)
2247+
await setUseStorageQuotasViaApi(true)
2248+
await publishDatasetViaApi(testDatasetIds.numericId)
2249+
await waitForNoLocks(testDatasetIds.numericId, 10)
2250+
})
2251+
2252+
afterAll(async () => {
2253+
await deletePublishedDatasetViaApi(testDatasetIds.persistentId).catch(() => undefined)
2254+
await deleteCollectionViaApi(testCollectionAlias).catch(() => undefined)
2255+
})
2256+
2257+
test('should return upload limits for dataset (quota configured)', async () => {
2258+
await setDatasetStorageSizeViaApi(testDatasetIds.numericId, testCollectionStorageQuotaInBytes)
2259+
const uploadLimits = await sut.getDatasetUploadLimits(testDatasetIds.numericId)
2260+
2261+
expect(uploadLimits).toBeDefined()
2262+
expect(typeof uploadLimits.storageQuotaRemaining).toBe('number')
2263+
expect(uploadLimits.storageQuotaRemaining).toBeLessThanOrEqual(
2264+
testCollectionStorageQuotaInBytes
2265+
)
2266+
expect(uploadLimits.storageQuotaRemaining).toBeGreaterThanOrEqual(0)
2267+
})
2268+
2269+
test('should return error when dataset does not exist', async () => {
2270+
await expect(sut.getDatasetUploadLimits(nonExistentTestDatasetId)).rejects.toBeInstanceOf(
2271+
ReadError
2272+
)
2273+
})
2274+
})
22322275
})

test/testHelpers/datasets/datasetHelper.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,45 @@ export const publishDatasetViaApi = async (datasetId: number): Promise<AxiosResp
306306
}
307307
}
308308

309+
export const setUseStorageQuotasViaApi = async (
310+
useStorageQuotas: boolean
311+
): Promise<AxiosResponse> => {
312+
try {
313+
return await axios.put(
314+
`${TestConstants.TEST_API_URL}/admin/settings/:UseStorageQuotas`,
315+
`${useStorageQuotas}`,
316+
{
317+
headers: {
318+
'Content-Type': 'text/plain',
319+
'X-Dataverse-Key': process.env.TEST_API_KEY
320+
}
321+
}
322+
)
323+
} catch (error) {
324+
throw new Error(`Error while setting UseStorageQuotas to ${useStorageQuotas}`)
325+
}
326+
}
327+
328+
export const setDatasetStorageSizeViaApi = async (
329+
datasetId: number | string,
330+
sizeInBytes: number
331+
): Promise<AxiosResponse> => {
332+
try {
333+
return await axios.put(
334+
`${TestConstants.TEST_API_URL}/datasets/${datasetId}/storage/quota`,
335+
`${sizeInBytes}`,
336+
{
337+
headers: {
338+
'Content-Type': 'text/plain',
339+
'X-Dataverse-Key': process.env.TEST_API_KEY
340+
}
341+
}
342+
)
343+
} catch (error) {
344+
throw new Error(`Error while setting storage quota for dataset ${datasetId}`)
345+
}
346+
}
347+
309348
export const deaccessionDatasetViaApi = async (
310349
datasetId: number,
311350
versionId: string
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { GetDatasetUploadLimits } from '../../../src/datasets/domain/useCases/GetDatasetUploadLimits'
2+
import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository'
3+
import { DatasetUploadLimits } from '../../../src/datasets/domain/models/DatasetUploadLimits'
4+
import { ReadError } from '../../../src/core/domain/repositories/ReadError'
5+
6+
describe('GetDatasetUploadLimits (unit)', () => {
7+
const testUploadLimits: DatasetUploadLimits = {
8+
numberOfFilesRemaining: 25,
9+
storageQuotaRemaining: 21474836480
10+
}
11+
12+
test('should return upload limits on repository success', async () => {
13+
const datasetsRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository
14+
datasetsRepositoryStub.getDatasetUploadLimits = jest.fn().mockResolvedValue(testUploadLimits)
15+
const sut = new GetDatasetUploadLimits(datasetsRepositoryStub)
16+
17+
const actual = await sut.execute(1)
18+
19+
expect(actual).toEqual(testUploadLimits)
20+
expect(actual.numberOfFilesRemaining).toBe(25)
21+
expect(actual.storageQuotaRemaining).toBe(21474836480)
22+
})
23+
24+
test('should return upload limits when using persistent id', async () => {
25+
const datasetsRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository
26+
datasetsRepositoryStub.getDatasetUploadLimits = jest.fn().mockResolvedValue(testUploadLimits)
27+
const sut = new GetDatasetUploadLimits(datasetsRepositoryStub)
28+
29+
const actual = await sut.execute('doi:10.77777/FK2/AAAAAA')
30+
31+
expect(actual).toEqual(testUploadLimits)
32+
})
33+
34+
test('should return error result on repository error', async () => {
35+
const datasetsRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository
36+
datasetsRepositoryStub.getDatasetUploadLimits = jest
37+
.fn()
38+
.mockRejectedValue(new ReadError('[404] Dataset not found'))
39+
const sut = new GetDatasetUploadLimits(datasetsRepositoryStub)
40+
41+
await expect(sut.execute(1)).rejects.toThrow(ReadError)
42+
})
43+
})

0 commit comments

Comments
 (0)