Skip to content

Commit 5d88a09

Browse files
authored
Merge pull request #423 from nfdi4health/fix/update-collection-dto-partial
Fix/update collection dto partial
2 parents 1dc5d8b + 63ab01b commit 5d88a09

6 files changed

Lines changed: 130 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel
4242
- Templates: Rename `CreateDatasetTemplateDTO` to `CreateTemplateDTO`.
4343
- Templates: Rename `createDatasetTemplate` repository method to `createTemplate`.
4444
- Templates: Rename `getDatasetTemplates` repository method to `getTemplatesByCollectionId`.
45+
- Collections: `updateCollection` now supports partial updates by accepting `Partial<CollectionDTO>`. Only explicitly provided fields are sent in update requests, aligning with Dataverse API semantics. Metadata blocks handling was adjusted to respect inheritance flags and avoid invalid field combinations.
4546

4647
### Fixed
4748

src/collections/domain/repositories/ICollectionsRepository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export interface ICollectionsRepository {
4444
): Promise<MyDataCollectionItemSubset>
4545
updateCollection(
4646
collectionIdOrAlias: number | string,
47-
updatedCollection: CollectionDTO
47+
updatedCollection: Partial<CollectionDTO>
4848
): Promise<void>
4949
getCollectionFeaturedItems(collectionIdOrAlias: number | string): Promise<FeaturedItem[]>
5050
updateCollectionFeaturedItems(

src/collections/domain/useCases/UpdateCollection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class UpdateCollection implements UseCase<void> {
1919
*/
2020
async execute(
2121
collectionIdOrAlias: number | string,
22-
updatedCollection: CollectionDTO
22+
updatedCollection: Partial<CollectionDTO>
2323
): Promise<void> {
2424
return await this.collectionsRepository.updateCollection(collectionIdOrAlias, updatedCollection)
2525
}

src/collections/infra/repositories/CollectionsRepository.ts

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,9 @@ export class CollectionsRepository extends ApiRepository implements ICollections
215215

216216
public async updateCollection(
217217
collectionIdOrAlias: string | number,
218-
updatedCollection: CollectionDTO
218+
updatedCollection: Partial<CollectionDTO>
219219
): Promise<void> {
220-
const requestBody = this.createCreateOrUpdateRequestBody(updatedCollection)
220+
const requestBody = this.createUpdateRequestBody(updatedCollection)
221221

222222
return this.doPut(`/${this.collectionsResourceName}/${collectionIdOrAlias}`, requestBody)
223223
.then(() => undefined)
@@ -332,6 +332,86 @@ export class CollectionsRepository extends ApiRepository implements ICollections
332332
}
333333
}
334334

335+
private createUpdateRequestBody(
336+
collectionDTO: Partial<CollectionDTO>
337+
): Partial<NewCollectionRequestPayload> {
338+
const dataverseContacts: NewCollectionContactRequestPayload[] | undefined =
339+
collectionDTO.contacts?.map((contact) => ({
340+
contactEmail: contact
341+
}))
342+
const inputLevelsRequestBody: NewCollectionInputLevelRequestPayload[] | undefined =
343+
collectionDTO.inputLevels?.map((inputLevel) => ({
344+
datasetFieldTypeName: inputLevel.datasetFieldName,
345+
include: inputLevel.include,
346+
required: inputLevel.required
347+
}))
348+
let metadataBlocksRequestBody: Partial<NewCollectionMetadataBlocksRequestPayload> | undefined
349+
350+
const hasMetadataBlocksData =
351+
collectionDTO.metadataBlockNames !== undefined ||
352+
collectionDTO.facetIds !== undefined ||
353+
collectionDTO.inputLevels !== undefined ||
354+
collectionDTO.inheritMetadataBlocksFromParent !== undefined ||
355+
collectionDTO.inheritFacetsFromParent !== undefined
356+
357+
if (hasMetadataBlocksData) {
358+
metadataBlocksRequestBody = {}
359+
if (collectionDTO.inheritMetadataBlocksFromParent !== true) {
360+
if (collectionDTO.metadataBlockNames !== undefined) {
361+
metadataBlocksRequestBody.metadataBlockNames = collectionDTO.metadataBlockNames
362+
}
363+
if (inputLevelsRequestBody !== undefined) {
364+
metadataBlocksRequestBody.inputLevels = inputLevelsRequestBody
365+
}
366+
}
367+
if (collectionDTO.inheritFacetsFromParent !== true) {
368+
if (collectionDTO.facetIds !== undefined) {
369+
metadataBlocksRequestBody.facetIds = collectionDTO.facetIds
370+
}
371+
}
372+
if (collectionDTO.inheritMetadataBlocksFromParent !== undefined) {
373+
metadataBlocksRequestBody.inheritMetadataBlocksFromParent =
374+
collectionDTO.inheritMetadataBlocksFromParent
375+
}
376+
if (collectionDTO.inheritFacetsFromParent !== undefined) {
377+
metadataBlocksRequestBody.inheritFacetsFromParent = collectionDTO.inheritFacetsFromParent
378+
}
379+
}
380+
381+
// Build the final request body, only including defined fields
382+
const requestBody: Partial<NewCollectionRequestPayload> = {}
383+
384+
if (collectionDTO.alias !== undefined) {
385+
requestBody.alias = collectionDTO.alias
386+
}
387+
388+
if (collectionDTO.name !== undefined) {
389+
requestBody.name = collectionDTO.name
390+
}
391+
392+
if (dataverseContacts !== undefined) {
393+
requestBody.dataverseContacts = dataverseContacts
394+
}
395+
396+
if (collectionDTO.type !== undefined) {
397+
requestBody.dataverseType = collectionDTO.type
398+
}
399+
400+
if (collectionDTO.description !== undefined) {
401+
requestBody.description = collectionDTO.description
402+
}
403+
404+
if (collectionDTO.affiliation !== undefined) {
405+
requestBody.affiliation = collectionDTO.affiliation
406+
}
407+
408+
if (metadataBlocksRequestBody !== undefined) {
409+
requestBody.metadataBlocks = metadataBlocksRequestBody
410+
}
411+
412+
return requestBody
413+
}
414+
335415
private applyCollectionSearchCriteriaToQueryParams(
336416
queryParams: URLSearchParams,
337417
collectionSearchCriteria: CollectionSearchCriteria

test/functional/collections/UpdateCollection.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {
33
WriteError,
44
createCollection,
55
getCollection,
6-
updateCollection
6+
updateCollection,
7+
CollectionDTO
78
} from '../../../src'
89
import { TestConstants } from '../../testHelpers/TestConstants'
910
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
@@ -35,6 +36,28 @@ describe('execute', () => {
3536
}
3637
})
3738

39+
test('should successfully update a collection with partial data (name only)', async () => {
40+
const testNewCollectionAlias = 'updateCollection-partial-test'
41+
const testNewCollection = createCollectionDTO(testNewCollectionAlias)
42+
await createCollection.execute(testNewCollection)
43+
44+
const partialUpdate: Partial<CollectionDTO> = {
45+
name: 'Partially Updated Name'
46+
}
47+
48+
expect.assertions(3)
49+
try {
50+
await updateCollection.execute(testNewCollectionAlias, partialUpdate)
51+
} catch (error) {
52+
throw new Error('Collection should be updated with partial data')
53+
} finally {
54+
const updatedCollection = await getCollection.execute(testNewCollectionAlias)
55+
expect(updatedCollection.name).toBe('Partially Updated Name')
56+
expect(updatedCollection.alias).toBe(testNewCollectionAlias)
57+
expect(updatedCollection.type).toBe(testNewCollection.type)
58+
}
59+
})
60+
3861
test('should throw an error when the parent collection does not exist', async () => {
3962
const testNewCollection = createCollectionDTO()
4063
expect.assertions(2)

test/integration/collections/CollectionsRepository.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,27 @@ describe('CollectionsRepository', () => {
11101110
expect(updatedInputLevel?.required).toBe(false)
11111111
})
11121112

1113+
test('should update collection with only partial fields (name and affiliation)', async () => {
1114+
const collectionDTO = createCollectionDTO('partial-update-test')
1115+
const testCollectionId = await sut.createCollection(collectionDTO)
1116+
const createdCollection = await sut.getCollection(testCollectionId)
1117+
const partialUpdate: Partial<CollectionDTO> = {
1118+
name: 'Partially Updated Name',
1119+
affiliation: 'New Affiliation'
1120+
}
1121+
1122+
await sut.updateCollection(testCollectionId, partialUpdate)
1123+
const updatedCollection = await sut.getCollection(testCollectionId)
1124+
1125+
expect(updatedCollection.name).toBe('Partially Updated Name')
1126+
expect(updatedCollection.affiliation).toBe('New Affiliation')
1127+
expect(updatedCollection.alias).toBe(createdCollection.alias)
1128+
expect(updatedCollection.type).toBe(createdCollection.type)
1129+
expect(updatedCollection.contacts).toEqual(createdCollection.contacts)
1130+
1131+
await deleteCollectionViaApi(collectionDTO.alias)
1132+
})
1133+
11131134
test('should update the collection to inherit metadata blocks from parent collection', async () => {
11141135
const parentCollectionAlias = 'inherit-metablocks-parent-update'
11151136
const parentCollectionDTO = createCollectionDTO(parentCollectionAlias)

0 commit comments

Comments
 (0)