Skip to content

Commit de4a677

Browse files
committed
resolve merge conflicts
2 parents 68c9f95 + 67d3b0e commit de4a677

File tree

10 files changed

+320
-33
lines changed

10 files changed

+320
-33
lines changed

docs/useCases.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ The different use cases currently available in the package are classified below,
1616
- [List All Collection Items](#list-all-collection-items)
1717
- [Collections write use cases](#collections-write-use-cases)
1818
- [Create a Collection](#create-a-collection)
19+
- [Update a Collection](#update-a-collection)
1920
- [Publish a Collection](#publish-a-collection)
2021
- [Datasets](#Datasets)
2122
- [Datasets read use cases](#datasets-read-use-cases)
@@ -233,6 +234,34 @@ The above example creates the new collection in the root collection since no col
233234

234235
The use case returns a number, which is the identifier of the created collection.
235236

237+
#### Update a Collection
238+
239+
Updates an existing collection, given a collection identifier and a [CollectionDTO](../src/collections/domain/dtos/CollectionDTO.ts) including the updated collection data.
240+
241+
##### Example call:
242+
243+
```typescript
244+
import { updateCollection } from '@iqss/dataverse-client-javascript'
245+
246+
/* ... */
247+
248+
const collectionIdOrAlias = 12345
249+
const collectionDTO: CollectionDTO = {
250+
alias: alias,
251+
name: 'Updated Collection Name',
252+
contacts: ['[email protected]'],
253+
type: CollectionType.DEPARTMENT
254+
}
255+
256+
updateCollection.execute(collectionIdOrAlias, collectionDTO)
257+
258+
/* ... */
259+
```
260+
261+
_See [use case](../src/collections/domain/useCases/UpdateCollection.ts) implementation_.
262+
263+
The `collectionIdOrAlias` is a generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId).
264+
236265
#### Publish a Collection
237266

238267
Publishes a Collection, given the collection identifier.

src/collections/domain/repositories/ICollectionsRepository.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@ export interface ICollectionsRepository {
2222
offset?: number,
2323
collectionSearchCriteria?: CollectionSearchCriteria
2424
): Promise<CollectionItemSubset>
25+
updateCollection(
26+
collectionIdOrAlias: number | string,
27+
updatedCollection: CollectionDTO
28+
): Promise<void>
2529
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { CollectionDTO } from '../dtos/CollectionDTO'
3+
import { ICollectionsRepository } from '../repositories/ICollectionsRepository'
4+
5+
export class UpdateCollection implements UseCase<void> {
6+
private collectionsRepository: ICollectionsRepository
7+
8+
constructor(collectionsRepository: ICollectionsRepository) {
9+
this.collectionsRepository = collectionsRepository
10+
}
11+
12+
/**
13+
* Updates an existing collection, given a collection identifier and a CollectionDTO including the updated collection data.
14+
*
15+
* @param {number | string} [collectionIdOrAlias] - A generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId)
16+
* @param {CollectionDTO} [newCollection] - CollectionDTO object including the updated collection data.
17+
* @returns {Promise<void>} -This method does not return anything upon successful completion.
18+
* @throws {WriteError} - If there are errors while writing data.
19+
*/
20+
async execute(
21+
collectionIdOrAlias: number | string,
22+
updatedCollection: CollectionDTO
23+
): Promise<void> {
24+
return await this.collectionsRepository.updateCollection(collectionIdOrAlias, updatedCollection)
25+
}
26+
}

src/collections/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { GetCollectionFacets } from './domain/useCases/GetCollectionFacets'
44
import { GetCollectionUserPermissions } from './domain/useCases/GetCollectionUserPermissions'
55
import { GetCollectionItems } from './domain/useCases/GetCollectionItems'
66
import { PublishCollection } from './domain/useCases/PublishCollection'
7+
import { UpdateCollection } from './domain/useCases/UpdateCollection'
78

89
import { CollectionsRepository } from './infra/repositories/CollectionsRepository'
910

@@ -15,14 +16,16 @@ const getCollectionFacets = new GetCollectionFacets(collectionsRepository)
1516
const getCollectionUserPermissions = new GetCollectionUserPermissions(collectionsRepository)
1617
const getCollectionItems = new GetCollectionItems(collectionsRepository)
1718
const publishCollection = new PublishCollection(collectionsRepository)
19+
const updateCollection = new UpdateCollection(collectionsRepository)
1820

1921
export {
2022
getCollection,
2123
createCollection,
2224
getCollectionFacets,
2325
getCollectionUserPermissions,
2426
getCollectionItems,
25-
publishCollection
27+
publishCollection,
28+
updateCollection
2629
}
2730
export { Collection, CollectionInputLevel } from './domain/models/Collection'
2831
export { CollectionFacet } from './domain/models/CollectionFacet'

src/collections/infra/repositories/CollectionsRepository.ts

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -67,36 +67,7 @@ export class CollectionsRepository extends ApiRepository implements ICollections
6767
collectionDTO: CollectionDTO,
6868
parentCollectionId: number | string = ROOT_COLLECTION_ID
6969
): Promise<number> {
70-
const dataverseContacts: NewCollectionContactRequestPayload[] = collectionDTO.contacts.map(
71-
(contact) => ({
72-
contactEmail: contact
73-
})
74-
)
75-
76-
const inputLevelsRequestBody: NewCollectionInputLevelRequestPayload[] =
77-
collectionDTO.inputLevels?.map((inputLevel) => ({
78-
datasetFieldTypeName: inputLevel.datasetFieldName,
79-
include: inputLevel.include,
80-
required: inputLevel.required
81-
}))
82-
83-
const requestBody: NewCollectionRequestPayload = {
84-
alias: collectionDTO.alias,
85-
name: collectionDTO.name,
86-
dataverseContacts: dataverseContacts,
87-
dataverseType: collectionDTO.type,
88-
...(collectionDTO.description && {
89-
description: collectionDTO.description
90-
}),
91-
...(collectionDTO.affiliation && {
92-
affiliation: collectionDTO.affiliation
93-
}),
94-
metadataBlocks: {
95-
metadataBlockNames: collectionDTO.metadataBlockNames,
96-
facetIds: collectionDTO.facetIds,
97-
inputLevels: inputLevelsRequestBody
98-
}
99-
}
70+
const requestBody = this.createCreateOrUpdateRequestBody(collectionDTO)
10071

10172
return this.doPost(`/${this.collectionsResourceName}/${parentCollectionId}`, requestBody)
10273
.then((response) => response.data.data.id)
@@ -185,6 +156,50 @@ export class CollectionsRepository extends ApiRepository implements ICollections
185156
})
186157
}
187158

159+
public async updateCollection(
160+
collectionIdOrAlias: string | number,
161+
updatedCollection: CollectionDTO
162+
): Promise<void> {
163+
const requestBody = this.createCreateOrUpdateRequestBody(updatedCollection)
164+
165+
return this.doPut(`/${this.collectionsResourceName}/${collectionIdOrAlias}`, requestBody)
166+
.then(() => undefined)
167+
.catch((error) => {
168+
throw error
169+
})
170+
}
171+
172+
private createCreateOrUpdateRequestBody(
173+
collectionDTO: CollectionDTO
174+
): NewCollectionRequestPayload {
175+
const dataverseContacts: NewCollectionContactRequestPayload[] = collectionDTO.contacts.map(
176+
(contact) => ({
177+
contactEmail: contact
178+
})
179+
)
180+
181+
const inputLevelsRequestBody: NewCollectionInputLevelRequestPayload[] =
182+
collectionDTO.inputLevels?.map((inputLevel) => ({
183+
datasetFieldTypeName: inputLevel.datasetFieldName,
184+
include: inputLevel.include,
185+
required: inputLevel.required
186+
}))
187+
188+
return {
189+
alias: collectionDTO.alias,
190+
name: collectionDTO.name,
191+
dataverseContacts: dataverseContacts,
192+
dataverseType: collectionDTO.type,
193+
...(collectionDTO.description && { description: collectionDTO.description }),
194+
...(collectionDTO.affiliation && { affiliation: collectionDTO.affiliation }),
195+
metadataBlocks: {
196+
metadataBlockNames: collectionDTO.metadataBlockNames,
197+
facetIds: collectionDTO.facetIds,
198+
inputLevels: inputLevelsRequestBody
199+
}
200+
}
201+
}
202+
188203
private applyCollectionSearchCriteriaToQueryParams(
189204
queryParams: GetCollectionItemsQueryParams,
190205
collectionSearchCriteria: CollectionSearchCriteria
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {
2+
ApiConfig,
3+
WriteError,
4+
createCollection,
5+
getCollection,
6+
updateCollection
7+
} from '../../../src'
8+
import { TestConstants } from '../../testHelpers/TestConstants'
9+
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
10+
import { createCollectionDTO } from '../../testHelpers/collections/collectionHelper'
11+
12+
describe('execute', () => {
13+
beforeEach(async () => {
14+
ApiConfig.init(
15+
TestConstants.TEST_API_URL,
16+
DataverseApiAuthMechanism.API_KEY,
17+
process.env.TEST_API_KEY
18+
)
19+
})
20+
21+
test('should successfully update a new collection', async () => {
22+
const testNewCollectionAlias = 'updateCollection-functional-test'
23+
const testNewCollection = createCollectionDTO(testNewCollectionAlias)
24+
await createCollection.execute(testNewCollection)
25+
const testNewName = 'Updated Name'
26+
testNewCollection.name = testNewName
27+
expect.assertions(1)
28+
try {
29+
await updateCollection.execute(testNewCollectionAlias, testNewCollection)
30+
} catch (error) {
31+
throw new Error('Collection should be updated')
32+
} finally {
33+
const updatedCollection = await getCollection.execute(testNewCollectionAlias)
34+
expect(updatedCollection.name).toBe(testNewName)
35+
}
36+
})
37+
38+
test('should throw an error when the parent collection does not exist', async () => {
39+
const testNewCollection = createCollectionDTO()
40+
expect.assertions(2)
41+
let writeError: WriteError
42+
try {
43+
await updateCollection.execute(TestConstants.TEST_DUMMY_COLLECTION_ID, testNewCollection)
44+
throw new Error('Use case should throw an error')
45+
} catch (error) {
46+
writeError = error
47+
} finally {
48+
expect(writeError).toBeInstanceOf(WriteError)
49+
expect(writeError.message).toEqual(
50+
`There was an error when writing the resource. Reason was: [404] Can't find dataverse with identifier='${TestConstants.TEST_DUMMY_COLLECTION_ID}'`
51+
)
52+
}
53+
})
54+
})

test/integration/collections/CollectionsRepository.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,4 +464,67 @@ describe('CollectionsRepository', () => {
464464
).rejects.toThrow(expectedError)
465465
})
466466
})
467+
468+
describe('updateCollection', () => {
469+
const testUpdatedCollectionAlias = 'updateCollection-test-updatedAlias'
470+
471+
afterAll(async () => {
472+
await deleteCollectionViaApi(testUpdatedCollectionAlias)
473+
})
474+
475+
test('should update the collection', async () => {
476+
// First we create a test collection using a CollectionDTO and createCollection method
477+
const collectionDTO = createCollectionDTO('updatedCollection-test-originalAlias')
478+
const testUpdateCollectionId = await sut.createCollection(collectionDTO)
479+
const createdCollection = await sut.getCollection(testUpdateCollectionId)
480+
expect(createdCollection.id).toBe(testUpdateCollectionId)
481+
expect(createdCollection.alias).toBe(collectionDTO.alias)
482+
expect(createdCollection.name).toBe(collectionDTO.name)
483+
expect(createdCollection.affiliation).toBe(collectionDTO.affiliation)
484+
expect(createdCollection.inputLevels?.length).toBe(1)
485+
const inputLevel = createdCollection.inputLevels?.[0]
486+
expect(inputLevel?.datasetFieldName).toBe('geographicCoverage')
487+
expect(inputLevel?.include).toBe(true)
488+
expect(inputLevel?.required).toBe(true)
489+
490+
// Now we update CollectionDTO and verify updates are correctly persisted after calling updateCollection method
491+
collectionDTO.alias = testUpdatedCollectionAlias
492+
const updatedCollectionName = 'updatedCollectionName'
493+
collectionDTO.name = updatedCollectionName
494+
const updatedCollectionAffiliation = 'updatedCollectionAffiliation'
495+
collectionDTO.affiliation = updatedCollectionAffiliation
496+
const updatedInputLevels = [
497+
{
498+
datasetFieldName: 'country',
499+
required: false,
500+
include: true
501+
}
502+
]
503+
collectionDTO.inputLevels = updatedInputLevels
504+
await sut.updateCollection(testUpdateCollectionId, collectionDTO)
505+
const updatedCollection = await sut.getCollection(testUpdateCollectionId)
506+
expect(updatedCollection.id).toBe(testUpdateCollectionId)
507+
expect(updatedCollection.alias).toBe(testUpdatedCollectionAlias)
508+
expect(updatedCollection.name).toBe(updatedCollectionName)
509+
expect(updatedCollection.affiliation).toBe(updatedCollectionAffiliation)
510+
expect(updatedCollection.inputLevels?.length).toBe(1)
511+
const updatedInputLevel = updatedCollection.inputLevels?.[0]
512+
expect(updatedInputLevel?.datasetFieldName).toBe('country')
513+
expect(updatedInputLevel?.include).toBe(true)
514+
expect(updatedInputLevel?.required).toBe(false)
515+
})
516+
517+
test('should return error when collection does not exist', async () => {
518+
const expectedError = new WriteError(
519+
`[404] Can't find dataverse with identifier='${TestConstants.TEST_DUMMY_COLLECTION_ID}'`
520+
)
521+
const testCollectionAlias = 'updateCollection-not-found-test'
522+
await expect(
523+
sut.updateCollection(
524+
TestConstants.TEST_DUMMY_COLLECTION_ID,
525+
createCollectionDTO(testCollectionAlias)
526+
)
527+
).rejects.toThrow(expectedError)
528+
})
529+
})
467530
})

test/integration/datasets/DatasetsRepository.test.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,14 @@ describe('DatasetsRepository', () => {
501501
directoryLabel: 'directoryLabel',
502502
categories: ['category1', 'category2']
503503
}
504+
505+
const uploadResponse = await uploadFileViaApi(
506+
testDatasetIds.numericId,
507+
testTextFile1Name,
508+
fileMetadata
509+
)
510+
511+
const fileId = uploadResponse.data.data.files[0].dataFile.id
504512
const expectedFilesAdded = [
505513
{
506514
fileName: 'test-file-1.txt',
@@ -510,10 +518,9 @@ describe('DatasetsRepository', () => {
510518
filePath: fileMetadata.directoryLabel,
511519
categories: fileMetadata.categories,
512520
MD5: '68b22040025784da775f55cfcb6dee2e',
513-
fileId: 50
521+
fileId: fileId
514522
}
515523
]
516-
await uploadFileViaApi(testDatasetIds.numericId, testTextFile1Name, fileMetadata)
517524
const actual = await sut.getDatasetVersionDiff(
518525
testDatasetIds.numericId,
519526
'1.0',

0 commit comments

Comments
 (0)