Skip to content

Commit 3b8e6ab

Browse files
committed
Merge branch 'develop' into 394-add-pagination-query-parameters-to-datasetfile-version-summaries-use-case
2 parents 050a268 + 3407247 commit 3b8e6ab

File tree

10 files changed

+302
-2
lines changed

10 files changed

+302
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel
99
### Added
1010

1111
- New Use Case: [Get Collections For Linking Use Case](./docs/useCases.md#get-collections-for-linking).
12+
- New Use Case: [Create a Dataset Template](./docs/useCases.md#create-a-dataset-template) under Collections.
1213

1314
### Changed
1415

docs/useCases.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The different use cases currently available in the package are classified below,
2525
- [Update Collection Featured Items](#update-collection-featured-items)
2626
- [Delete Collection Featured Items](#delete-collection-featured-items)
2727
- [Delete a Collection Featured Item](#delete-a-collection-featured-item)
28+
- [Create a Dataset Template](#create-a-dataset-template)
2829
- [Datasets](#Datasets)
2930
- [Datasets read use cases](#datasets-read-use-cases)
3031
- [Get a Dataset](#get-a-dataset)
@@ -567,6 +568,41 @@ deleteCollectionFeaturedItem.execute(featuredItemId)
567568

568569
_See [use case](../src/collections/domain/useCases/DeleteCollectionFeaturedItem.ts)_ definition.
569570

571+
#### Create a Dataset Template
572+
573+
Creates a dataset template for a given Dataverse collection id or alias.
574+
575+
##### Example call:
576+
577+
```typescript
578+
import { createDatasetTemplate } from '@iqss/dataverse-client-javascript'
579+
import { TemplateCreateDTO } from '@iqss/dataverse-client-javascript'
580+
581+
const collectionAlias = ':root'
582+
const template: TemplateCreateDTO = {
583+
name: 'Dataverse template',
584+
isDefault: true,
585+
fields: [
586+
{
587+
typeName: 'author',
588+
typeClass: 'compound',
589+
multiple: true,
590+
value: [
591+
{
592+
authorName: { typeName: 'authorName', value: 'Belicheck, Bill' },
593+
authorAffiliation: { typeName: 'authorIdentifierScheme', value: 'ORCID' }
594+
}
595+
]
596+
}
597+
],
598+
instructions: [{ instructionField: 'author', instructionText: 'The author data' }]
599+
}
600+
601+
await createDatasetTemplate.execute(template, collectionAlias)
602+
```
603+
604+
_See [use case](../src/collections/domain/useCases/CreateDatasetTemplate.ts) implementation_.
605+
570606
## Datasets
571607

572608
### Datasets Read Use Cases
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { MetadataFieldTypeClass } from '../../../metadataBlocks/domain/models/MetadataBlock'
2+
3+
export interface CreateDatasetTemplateDTO {
4+
name: string
5+
isDefault?: boolean
6+
fields?: TemplateFieldDTO[]
7+
instructions?: TemplateInstructionDTO[]
8+
}
9+
10+
export interface TemplateFieldDTO {
11+
typeName: string
12+
multiple: boolean
13+
typeClass?: MetadataFieldTypeClass
14+
value?: TemplateFieldValueDTO[]
15+
}
16+
17+
export interface TemplateFieldValueDTO {
18+
[key: string]:
19+
| TemplateFieldValuePrimitiveDTO
20+
| TemplateFieldValueCompoundDTO
21+
| TemplateFieldValueControlledVocabularyDTO
22+
}
23+
24+
export interface TemplateFieldValuePrimitiveDTO {
25+
typeName: string
26+
typeClass: MetadataFieldTypeClass.Primitive
27+
value: string | string[]
28+
}
29+
30+
export interface TemplateFieldValueCompoundDTO {
31+
typeName: string
32+
typeClass: MetadataFieldTypeClass.Compound
33+
value: TemplateFieldValueDTO[]
34+
}
35+
36+
export interface TemplateFieldValueControlledVocabularyDTO {
37+
typeName: string
38+
typeClass: MetadataFieldTypeClass.ControlledVocabulary
39+
value: string
40+
}
41+
42+
export interface TemplateInstructionDTO {
43+
instructionField: string
44+
instructionText: string
45+
}

src/collections/domain/repositories/ICollectionsRepository.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { CollectionItemType } from '../../../collections/domain/models/Collectio
1212
import { CollectionLinks } from '../models/CollectionLinks'
1313
import { CollectionSummary } from '../models/CollectionSummary'
1414
import { LinkingObjectType } from '../useCases/GetCollectionsForLinking'
15+
import { CreateDatasetTemplateDTO } from '../dtos/CreateDatasetTemplateDTO'
1516

1617
export interface ICollectionsRepository {
1718
getCollection(collectionIdOrAlias: number | string): Promise<Collection>
@@ -68,4 +69,8 @@ export interface ICollectionsRepository {
6869
searchTerm: string,
6970
alreadyLinked: boolean
7071
): Promise<CollectionSummary[]>
72+
createDatasetTemplate(
73+
collectionIdOrAlias: number | string,
74+
template: CreateDatasetTemplateDTO
75+
): Promise<void>
7176
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { ROOT_COLLECTION_ID } from '../models/Collection'
2+
import { UseCase } from '../../../core/domain/useCases/UseCase'
3+
import { ICollectionsRepository } from '../repositories/ICollectionsRepository'
4+
import { CreateDatasetTemplateDTO } from '../dtos/CreateDatasetTemplateDTO'
5+
6+
export class CreateDatasetTemplate implements UseCase<void> {
7+
private collectionsRepository: ICollectionsRepository
8+
9+
constructor(collectionsRepository: ICollectionsRepository) {
10+
this.collectionsRepository = collectionsRepository
11+
}
12+
13+
/**
14+
* Creates a Dataset Template in the specified collection.
15+
*
16+
* @param {CreateDatasetTemplateDTO} template - Template definition payload.
17+
* @param {number | string} [collectionIdOrAlias = ':root'] - A generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId)
18+
* If this parameter is not set, the default value is: ':root'.
19+
* @returns {Promise<void>}
20+
*/
21+
async execute(
22+
template: CreateDatasetTemplateDTO,
23+
collectionIdOrAlias: number | string = ROOT_COLLECTION_ID
24+
): Promise<void> {
25+
return await this.collectionsRepository.createDatasetTemplate(collectionIdOrAlias, template)
26+
}
27+
}

src/collections/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { LinkCollection } from './domain/useCases/LinkCollection'
1616
import { UnlinkCollection } from './domain/useCases/UnlinkCollection'
1717
import { GetCollectionLinks } from './domain/useCases/GetCollectionLinks'
1818
import { GetCollectionsForLinking } from './domain/useCases/GetCollectionsForLinking'
19+
import { CreateDatasetTemplate } from './domain/useCases/CreateDatasetTemplate'
1920

2021
const collectionsRepository = new CollectionsRepository()
2122

@@ -36,6 +37,7 @@ const linkCollection = new LinkCollection(collectionsRepository)
3637
const unlinkCollection = new UnlinkCollection(collectionsRepository)
3738
const getCollectionLinks = new GetCollectionLinks(collectionsRepository)
3839
const getCollectionsForLinking = new GetCollectionsForLinking(collectionsRepository)
40+
const createDatasetTemplate = new CreateDatasetTemplate(collectionsRepository)
3941

4042
export {
4143
getCollection,
@@ -54,7 +56,8 @@ export {
5456
linkCollection,
5557
unlinkCollection,
5658
getCollectionLinks,
57-
getCollectionsForLinking
59+
getCollectionsForLinking,
60+
createDatasetTemplate
5861
}
5962
export { Collection, CollectionInputLevel } from './domain/models/Collection'
6063
export { CollectionFacet } from './domain/models/CollectionFacet'

src/collections/infra/repositories/CollectionsRepository.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { ReadError } from '../../../core/domain/repositories/ReadError'
4040
import { CollectionLinks } from '../../domain/models/CollectionLinks'
4141
import { CollectionSummary } from '../../domain/models/CollectionSummary'
4242
import { LinkingObjectType } from '../../domain/useCases/GetCollectionsForLinking'
43+
import { CreateDatasetTemplateDTO } from '../../domain/dtos/CreateDatasetTemplateDTO'
4344

4445
export interface NewCollectionRequestPayload {
4546
alias: string
@@ -528,4 +529,18 @@ export class CollectionsRepository extends ApiRepository implements ICollections
528529
throw error
529530
})
530531
}
532+
533+
public async createDatasetTemplate(
534+
collectionIdOrAlias: number | string,
535+
template: CreateDatasetTemplateDTO
536+
): Promise<void> {
537+
return this.doPost(
538+
`/${this.collectionsResourceName}/${collectionIdOrAlias}/templates`,
539+
template
540+
)
541+
.then(() => undefined)
542+
.catch((error) => {
543+
throw error
544+
})
545+
}
531546
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { ApiConfig } from '../../../src'
2+
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
3+
import { TestConstants } from '../../testHelpers/TestConstants'
4+
import { getDatasetTemplates } from '../../../src/datasets'
5+
import { CreateDatasetTemplateDTO } from '../../../src/collections/domain/dtos/CreateDatasetTemplateDTO'
6+
import { createDatasetTemplate } from '../../../src/collections'
7+
import { MetadataFieldTypeClass } from '../../../src/metadataBlocks/domain/models/MetadataBlock'
8+
import { deleteDatasetTemplateViaApi } from '../../testHelpers/datasets/datasetTemplatesHelper'
9+
10+
describe('CreateTemplate.execute', () => {
11+
beforeEach(async () => {
12+
ApiConfig.init(
13+
TestConstants.TEST_API_URL,
14+
DataverseApiAuthMechanism.API_KEY,
15+
process.env.TEST_API_KEY
16+
)
17+
})
18+
19+
test('should create a template in :root with provided JSON', async () => {
20+
const templateDto: CreateDatasetTemplateDTO = {
21+
name: 'TestDataverse template',
22+
isDefault: true,
23+
fields: [
24+
{
25+
typeName: 'author',
26+
typeClass: MetadataFieldTypeClass.Compound,
27+
multiple: true,
28+
value: [
29+
{
30+
authorName: {
31+
typeName: 'authorName',
32+
typeClass: MetadataFieldTypeClass.Primitive,
33+
value: 'Belicheck, Bill'
34+
},
35+
authorAffiliation: {
36+
typeName: 'authorIdentifierScheme',
37+
typeClass: MetadataFieldTypeClass.Primitive,
38+
value: 'ORCID'
39+
}
40+
}
41+
]
42+
}
43+
],
44+
instructions: [
45+
{
46+
instructionField: 'author',
47+
instructionText: 'The author data'
48+
}
49+
]
50+
}
51+
await createDatasetTemplate.execute(templateDto)
52+
const templates = await getDatasetTemplates.execute(':root')
53+
54+
expect(templates[templates.length - 1].name).toBe(templateDto.name)
55+
expect(templates[templates.length - 1].isDefault).toBe(templateDto.isDefault)
56+
expect(templates[templates.length - 1].instructions.length).toBe(
57+
templateDto.instructions?.length ?? 0
58+
)
59+
60+
deleteDatasetTemplateViaApi(templates[templates.length - 1].id)
61+
})
62+
})

test/integration/collections/CollectionsRepository.test.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import {
1616
getDatasetFiles,
1717
restrictFile,
1818
deleteFile,
19-
linkDataset
19+
linkDataset,
20+
createDatasetTemplate,
21+
MetadataFieldTypeClass
2022
} from '../../../src'
2123
import { ApiConfig } from '../../../src'
2224
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
@@ -58,6 +60,9 @@ import {
5860
DvObjectFeaturedItemDTO,
5961
FeaturedItemsDTO
6062
} from '../../../src/collections/domain/dtos/FeaturedItemsDTO'
63+
import { CreateDatasetTemplateDTO } from '../../../src/collections/domain/dtos/CreateDatasetTemplateDTO'
64+
import { getDatasetTemplates } from '../../../src/datasets'
65+
import { deleteDatasetTemplateViaApi } from '../../testHelpers/datasets/datasetTemplatesHelper'
6166

6267
describe('CollectionsRepository', () => {
6368
const testCollectionAlias = 'collectionsRepositoryTestCollection'
@@ -2143,4 +2148,59 @@ describe('CollectionsRepository', () => {
21432148
await expect(sut.getCollectionLinks(invalidCollectionId)).rejects.toThrow(expectedError)
21442149
})
21452150
})
2151+
2152+
describe('createDatasetTemplate', () => {
2153+
const templateDto: CreateDatasetTemplateDTO = {
2154+
name: 'CollectionsRepository template',
2155+
isDefault: true,
2156+
fields: [
2157+
{
2158+
typeName: 'author',
2159+
typeClass: MetadataFieldTypeClass.Compound,
2160+
multiple: true,
2161+
value: [
2162+
{
2163+
authorName: {
2164+
typeName: 'authorName',
2165+
typeClass: MetadataFieldTypeClass.Primitive,
2166+
value: 'Belicheck, Bill'
2167+
},
2168+
authorAffiliation: {
2169+
typeName: 'authorIdentifierScheme',
2170+
typeClass: MetadataFieldTypeClass.Primitive,
2171+
value: 'ORCID'
2172+
}
2173+
}
2174+
]
2175+
}
2176+
],
2177+
instructions: [
2178+
{
2179+
instructionField: 'author',
2180+
instructionText: 'The author data'
2181+
}
2182+
]
2183+
}
2184+
test('should create a template in :root with provided JSON', async () => {
2185+
await createDatasetTemplate.execute(templateDto)
2186+
const templates = await getDatasetTemplates.execute(':root')
2187+
2188+
expect(templates[templates.length - 1].name).toBe(templateDto.name)
2189+
expect(templates[templates.length - 1].isDefault).toBe(templateDto.isDefault)
2190+
expect(templates[templates.length - 1].instructions.length).toBe(
2191+
templateDto.instructions?.length ?? 0
2192+
)
2193+
2194+
deleteDatasetTemplateViaApi(templates[templates.length - 1].id)
2195+
})
2196+
2197+
test('should return error when creating a template with invalidCollectionAlias', async () => {
2198+
const expectedError = new WriteError(
2199+
`[404] Can't find dataverse with identifier='invalidCollectionAlias'`
2200+
)
2201+
await expect(
2202+
createDatasetTemplate.execute(templateDto, 'invalidCollectionAlias')
2203+
).rejects.toThrow(expectedError)
2204+
})
2205+
})
21462206
})
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { CreateDatasetTemplate } from '../../../src/collections/domain/useCases/CreateDatasetTemplate'
2+
import { ICollectionsRepository } from '../../../src/collections/domain/repositories/ICollectionsRepository'
3+
import { CreateDatasetTemplateDTO } from '../../../src/collections/domain/dtos/CreateDatasetTemplateDTO'
4+
import { WriteError } from '../../../src'
5+
6+
describe('execute', () => {
7+
const testTemplateDTO = { name: 't' } as CreateDatasetTemplateDTO
8+
const testCollectionId = 1
9+
10+
test('should return undefined when repository call is successful', async () => {
11+
const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository
12+
collectionRepositoryStub.createDatasetTemplate = jest.fn().mockResolvedValue(testCollectionId)
13+
const sut = new CreateDatasetTemplate(collectionRepositoryStub)
14+
15+
const actual = await sut.execute(testTemplateDTO)
16+
17+
expect(collectionRepositoryStub.createDatasetTemplate).toHaveBeenCalledWith(
18+
':root',
19+
testTemplateDTO
20+
)
21+
expect(actual).toEqual(testCollectionId)
22+
})
23+
24+
test('should call repository with provided collection id/alias', async () => {
25+
const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository
26+
collectionRepositoryStub.createDatasetTemplate = jest.fn().mockResolvedValue(testCollectionId)
27+
28+
const sut = new CreateDatasetTemplate(collectionRepositoryStub)
29+
const actual = await sut.execute(testTemplateDTO, 'alias123')
30+
31+
expect(collectionRepositoryStub.createDatasetTemplate).toHaveBeenCalledWith(
32+
'alias123',
33+
testTemplateDTO
34+
)
35+
36+
expect(actual).toEqual(testCollectionId)
37+
})
38+
39+
test('should return error result on repository error', async () => {
40+
const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository
41+
collectionRepositoryStub.createDatasetTemplate = jest.fn().mockRejectedValue(new WriteError())
42+
const testCreateTemplate = new CreateDatasetTemplate(collectionRepositoryStub)
43+
44+
await expect(testCreateTemplate.execute(testTemplateDTO)).rejects.toThrow(WriteError)
45+
})
46+
})

0 commit comments

Comments
 (0)