Skip to content

Commit 67d880f

Browse files
authored
Merge pull request #232 from IQSS/231-implement-use-case-for-sending-information-to-contacts
implement use case for sending information to contacts
2 parents 9d043ca + 58cadf2 commit 67d880f

File tree

11 files changed

+372
-0
lines changed

11 files changed

+372
-0
lines changed

docs/useCases.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ The different use cases currently available in the package are classified below,
6868
- [Get Dataverse Backend Version](#get-dataverse-backend-version)
6969
- [Get Maximum Embargo Duration In Months](#get-maximum-embargo-duration-in-months)
7070
- [Get ZIP Download Limit](#get-zip-download-limit)
71+
- [Contact](#Contact)
72+
- [Send Feedback to Object Contacts](#send-feedback-to-object-contacts)
7173

7274
## Collections
7375

@@ -1499,3 +1501,40 @@ getZipDownloadLimit.execute().then((downloadLimit: number) => {
14991501
```
15001502

15011503
_See [use case](../src/info/domain/useCases/GetZipDownloadLimit.ts) implementation_.
1504+
1505+
## Contact
1506+
1507+
#### Send Feedback to Object Contacts
1508+
1509+
Returns a [Contact](../src/contactInfo/domain/models/Contact.ts) object, which contains contact return information, showing fromEmail, subject, body.
1510+
1511+
##### Example call:
1512+
1513+
```typescript
1514+
import { submitContactInfo } from '@iqss/dataverse-client-javascript'
1515+
1516+
/* ... */
1517+
1518+
const contactDTO: ContactDTO = {
1519+
targedId: 1
1520+
subject: 'Data Question',
1521+
body: 'Please help me understand your data. Thank you!',
1522+
fromEmail: '[email protected]'
1523+
}
1524+
1525+
submitContactInfo.execute(contactDTO)
1526+
1527+
/* ... */
1528+
```
1529+
1530+
_See [use case](../src/info/domain/useCases/submitContactInfo.ts) implementation_.
1531+
1532+
The above example would submit feedback to all contacts of a object where the object targetId = 1.
1533+
1534+
In ContactDTO, it takes the following information:
1535+
1536+
- **targetId**: the numeric identifier of the collection, dataset, or datafile. Persistent ids and collection aliases are not supported. (Optional)
1537+
- **identifier**: the alias of a collection or the persistence id of a dataset or datafile. (Optional)
1538+
- **subject**: the email subject line.
1539+
- **body**: the email body to send.
1540+
- **fromEmail**: the email to list in the reply-to field.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export interface ContactDTO {
2+
targetId?: number
3+
identifier?: string
4+
subject: string
5+
body: string
6+
fromEmail: string
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface Contact {
2+
fromEmail: string
3+
body: string
4+
subject: string
5+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { Contact } from '../models/Contact'
2+
import { ContactDTO } from '../dtos/ContactDTO'
3+
4+
export interface IContactRepository {
5+
submitContactInfo(contactDTO: ContactDTO): Promise<Contact[]>
6+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { UseCase } from '../../../core/domain/useCases/UseCase'
2+
import { ContactDTO } from '../dtos/ContactDTO'
3+
import { Contact } from '../models/Contact'
4+
import { IContactRepository } from '../repositories/IContactRepository'
5+
6+
export class SubmitContactInfo implements UseCase<Contact[]> {
7+
private contactRepository: IContactRepository
8+
9+
constructor(contactRepository: IContactRepository) {
10+
this.contactRepository = contactRepository
11+
}
12+
13+
/**
14+
* Submits contact information and returns a Contact model containing the submitted data.
15+
*
16+
* @param {ContactDTO} contactDTO - The contact information to be submitted.
17+
* @returns {Promise<Contact[]>} A promise resolving to a list of contact.
18+
*/
19+
20+
async execute(contactDTO: ContactDTO): Promise<Contact[]> {
21+
return await this.contactRepository.submitContactInfo(contactDTO)
22+
}
23+
}

src/contactInfo/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { SubmitContactInfo } from './domain/useCases/SubmitContactInfo'
2+
import { ContactRepository } from './infra/repositories/ContactRepository'
3+
4+
const contactRepository = new ContactRepository()
5+
const submitContactInfo = new SubmitContactInfo(contactRepository)
6+
7+
export { submitContactInfo }
8+
export { Contact } from './domain/models/Contact'
9+
export { ContactDTO } from './domain/dtos/ContactDTO'
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ApiRepository } from '../../../core/infra/repositories/ApiRepository'
2+
import { Contact } from '../../domain/models/Contact'
3+
import { IContactRepository } from '../../domain/repositories/IContactRepository'
4+
import { ContactDTO } from '../../domain/dtos/ContactDTO'
5+
6+
export class ContactRepository extends ApiRepository implements IContactRepository {
7+
public async submitContactInfo(contactDTO: ContactDTO): Promise<Contact[]> {
8+
return this.doPost(`/sendfeedback`, contactDTO)
9+
.then((response) => {
10+
const responseData = response.data
11+
const contact: Contact[] = responseData.data.map((item: Contact) => ({
12+
fromEmail: item.fromEmail,
13+
subject: item.subject,
14+
body: item.body
15+
}))
16+
17+
return contact
18+
})
19+
.catch((error) => {
20+
throw error
21+
})
22+
}
23+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './datasets'
66
export * from './collections'
77
export * from './metadataBlocks'
88
export * from './files'
9+
export * from './contactInfo'
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { ApiConfig, submitContactInfo, ContactDTO, WriteError, Contact } from '../../../src'
2+
import { TestConstants } from '../../testHelpers/TestConstants'
3+
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
4+
5+
describe('submitContactInfo', () => {
6+
beforeAll(async () => {
7+
ApiConfig.init(
8+
TestConstants.TEST_API_URL,
9+
DataverseApiAuthMechanism.API_KEY,
10+
process.env.TEST_API_KEY
11+
)
12+
})
13+
14+
test('should return Contact result successfully with targetId', async () => {
15+
const contactDTO: ContactDTO = {
16+
targetId: 1,
17+
subject: 'Data Question',
18+
body: 'Please help me understand your data. Thank you!',
19+
fromEmail: '[email protected]'
20+
}
21+
22+
let contactInfo: Contact[] = []
23+
try {
24+
contactInfo = await submitContactInfo.execute(contactDTO)
25+
} catch (error) {
26+
throw new Error('Contact info should be submitted')
27+
} finally {
28+
expect(contactInfo).toBeDefined()
29+
expect(contactInfo[0].fromEmail).toEqual('[email protected]')
30+
expect(contactInfo[0].subject).toEqual(expect.any(String))
31+
expect(contactInfo[0].body).toEqual(expect.any(String))
32+
}
33+
})
34+
35+
test('should return a Contact when targetId is not provided', async () => {
36+
const contactDTO: ContactDTO = {
37+
subject: 'General Inquiry',
38+
body: 'I have a general question.',
39+
fromEmail: '[email protected]'
40+
}
41+
42+
let contactInfo: Contact[] = []
43+
try {
44+
contactInfo = await submitContactInfo.execute(contactDTO)
45+
} catch (error) {
46+
throw new Error('Contact info should be submitted even if target id is missing')
47+
} finally {
48+
expect(contactInfo).toBeDefined()
49+
expect(contactInfo[0].fromEmail).toEqual('[email protected]')
50+
expect(contactInfo[0].subject).toEqual(expect.any(String))
51+
expect(contactInfo[0].body).toEqual(expect.any(String))
52+
}
53+
})
54+
55+
test('should return Contact when contact info is successfully submitted with identifier', async () => {
56+
const test2ContactDTO: ContactDTO = {
57+
identifier: 'root',
58+
subject: 'Data Question',
59+
body: 'Please help me understand your data. Thank you!',
60+
fromEmail: '[email protected]'
61+
}
62+
const contactInfo = await submitContactInfo.execute(test2ContactDTO)
63+
64+
expect(contactInfo).toBeDefined()
65+
expect(contactInfo[0].fromEmail).toEqual(test2ContactDTO.fromEmail)
66+
expect(contactInfo[0].subject).toEqual(expect.any(String))
67+
expect(contactInfo[0].body).toEqual(expect.any(String))
68+
})
69+
70+
test('should return error if the target id is unexisted', async () => {
71+
const contactDTO: ContactDTO = {
72+
targetId: 0,
73+
subject: '',
74+
body: '',
75+
fromEmail: '[email protected]'
76+
}
77+
const expectedError = new WriteError(`[400] Feedback target object not found.`)
78+
await expect(submitContactInfo.execute(contactDTO)).rejects.toThrow(expectedError)
79+
})
80+
})
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { ContactRepository } from '../../../src/contactInfo/infra/repositories/ContactRepository'
2+
import { ApiConfig, Contact, ContactDTO, WriteError } from '../../../src'
3+
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
4+
import { TestConstants } from '../../testHelpers/TestConstants'
5+
6+
describe('submitContactInfo', () => {
7+
beforeAll(async () => {
8+
ApiConfig.init(
9+
TestConstants.TEST_API_URL,
10+
DataverseApiAuthMechanism.API_KEY,
11+
process.env.TEST_API_KEY
12+
)
13+
})
14+
15+
const testContactDTO: ContactDTO = {
16+
targetId: 1,
17+
subject: 'Data Question',
18+
body: 'Please help me understand your data. Thank you!',
19+
fromEmail: '[email protected]'
20+
}
21+
22+
const test2ContactDTO: ContactDTO = {
23+
identifier: 'root',
24+
subject: 'Data Question',
25+
body: 'Please help me understand your data. Thank you!',
26+
fromEmail: '[email protected]'
27+
}
28+
29+
const sut: ContactRepository = new ContactRepository()
30+
31+
test('should return Contact when contact info is successfully submitted', async () => {
32+
const contactInfo = await sut.submitContactInfo(test2ContactDTO)
33+
34+
expect(contactInfo).toBeDefined()
35+
expect(contactInfo[0].fromEmail).toEqual(testContactDTO.fromEmail)
36+
expect(contactInfo[0].subject).toEqual(expect.any(String))
37+
expect(contactInfo[0].body).toEqual(expect.any(String))
38+
})
39+
40+
test('should return Contact when contact info is successfully submitted if accessed by identifier', async () => {
41+
const contactInfo = await sut.submitContactInfo(testContactDTO)
42+
43+
expect(contactInfo).toBeDefined()
44+
expect(contactInfo[0].fromEmail).toEqual(test2ContactDTO.fromEmail)
45+
expect(contactInfo[0].subject).toEqual(expect.any(String))
46+
expect(contactInfo[0].body).toEqual(expect.any(String))
47+
})
48+
49+
test('should return a Contact when targetId is not provided', async () => {
50+
const contactDTOWithoutTargetId: Partial<ContactDTO> = {
51+
subject: 'General Inquiry',
52+
body: 'I have a general question.',
53+
fromEmail: '[email protected]'
54+
}
55+
56+
const contactInfo: Contact[] = await sut.submitContactInfo(
57+
contactDTOWithoutTargetId as ContactDTO
58+
)
59+
60+
expect(contactInfo).toBeDefined()
61+
expect(contactInfo[0].fromEmail).toEqual(contactDTOWithoutTargetId.fromEmail)
62+
expect(contactInfo[0].subject).toEqual(expect.any(String))
63+
expect(contactInfo[0].body).toEqual(expect.any(String))
64+
})
65+
66+
test('should return error if the target id is unexisted', async () => {
67+
const invalidContactDTO: ContactDTO = {
68+
targetId: 0,
69+
subject: '',
70+
body: '',
71+
fromEmail: ''
72+
}
73+
const expectedError = new WriteError(`[400] Feedback target object not found.`)
74+
await expect(sut.submitContactInfo(invalidContactDTO)).rejects.toThrow(expectedError)
75+
})
76+
})

0 commit comments

Comments
 (0)