Skip to content

implement use case for sending information to contacts #232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ The different use cases currently available in the package are classified below,
- [Get Dataverse Backend Version](#get-dataverse-backend-version)
- [Get Maximum Embargo Duration In Months](#get-maximum-embargo-duration-in-months)
- [Get ZIP Download Limit](#get-zip-download-limit)
- [Contact](#Contact)
- [Send Feedback to Object Contacts](#send-feedback-to-object-contacts)

## Collections

Expand Down Expand Up @@ -1392,3 +1394,40 @@ getZipDownloadLimit.execute().then((downloadLimit: number) => {
```

_See [use case](../src/info/domain/useCases/GetZipDownloadLimit.ts) implementation_.

## Contact

#### Send Feedback to Object Contacts

Returns a [Contact](../src/contactInfo/domain/models/Contact.ts) object, which contains contact return information, showing fromEmail, subject, body.

##### Example call:

```typescript
import { submitContactInfo } from '@iqss/dataverse-client-javascript'

/* ... */

const contactDTO: ContactDTO = {
targedId: 1
subject: 'Data Question',
body: 'Please help me understand your data. Thank you!',
fromEmail: '[email protected]'
}

submitContactInfo.execute(contactDTO)

/* ... */
```

_See [use case](../src/info/domain/useCases/submitContactInfo.ts) implementation_.

The above example would submit feedback to all contacts of a object where the object targetId = 1.

In ContactDTO, it takes the following information:

- **targetId**: the numeric identifier of the collection, dataset, or datafile. Persistent ids and collection aliases are not supported. (Optional)
- **identifier**: the alias of a collection or the persistence id of a dataset or datafile. (Optional)
- **subject**: the email subject line.
- **body**: the email body to send.
- **fromEmail**: the email to list in the reply-to field.
7 changes: 7 additions & 0 deletions src/contactInfo/domain/dtos/ContactDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface ContactDTO {
targetId?: number
identifier?: string
subject: string
body: string
fromEmail: string
}
5 changes: 5 additions & 0 deletions src/contactInfo/domain/models/Contact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Contact {
fromEmail: string
body: string
subject: string
}
6 changes: 6 additions & 0 deletions src/contactInfo/domain/repositories/IContactRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Contact } from '../models/Contact'
import { ContactDTO } from '../dtos/ContactDTO'

export interface IContactRepository {
submitContactInfo(contactDTO: ContactDTO): Promise<Contact[]>
}
23 changes: 23 additions & 0 deletions src/contactInfo/domain/useCases/SubmitContactInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { ContactDTO } from '../dtos/ContactDTO'
import { Contact } from '../models/Contact'
import { IContactRepository } from '../repositories/IContactRepository'

export class SubmitContactInfo implements UseCase<Contact[]> {
private contactRepository: IContactRepository

constructor(contactRepository: IContactRepository) {
this.contactRepository = contactRepository
}

/**
* Submits contact information and returns a Contact model containing the submitted data.
*
* @param {ContactDTO} contactDTO - The contact information to be submitted.
* @returns {Promise<Contact[]>} A promise resolving to a list of contact.
*/

async execute(contactDTO: ContactDTO): Promise<Contact[]> {
return await this.contactRepository.submitContactInfo(contactDTO)
}
}
9 changes: 9 additions & 0 deletions src/contactInfo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SubmitContactInfo } from './domain/useCases/SubmitContactInfo'
import { ContactRepository } from './infra/repositories/ContactRepository'

const contactRepository = new ContactRepository()
const submitContactInfo = new SubmitContactInfo(contactRepository)

export { submitContactInfo }
export { Contact } from './domain/models/Contact'
export { ContactDTO } from './domain/dtos/ContactDTO'
23 changes: 23 additions & 0 deletions src/contactInfo/infra/repositories/ContactRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ApiRepository } from '../../../core/infra/repositories/ApiRepository'
import { Contact } from '../../domain/models/Contact'
import { IContactRepository } from '../../domain/repositories/IContactRepository'
import { ContactDTO } from '../../domain/dtos/ContactDTO'

export class ContactRepository extends ApiRepository implements IContactRepository {
public async submitContactInfo(contactDTO: ContactDTO): Promise<Contact[]> {
return this.doPost(`/sendfeedback`, contactDTO)
.then((response) => {
const responseData = response.data
const contact: Contact[] = responseData.data.map((item: Contact) => ({
fromEmail: item.fromEmail,
subject: item.subject,
body: item.body
}))

return contact
})
.catch((error) => {
throw error
})
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './datasets'
export * from './collections'
export * from './metadataBlocks'
export * from './files'
export * from './contactInfo'
80 changes: 80 additions & 0 deletions test/functional/contact/SubmitContactInfo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { ApiConfig, submitContactInfo, ContactDTO, WriteError, Contact } from '../../../src'
import { TestConstants } from '../../testHelpers/TestConstants'
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'

describe('submitContactInfo', () => {
beforeAll(async () => {
ApiConfig.init(
TestConstants.TEST_API_URL,
DataverseApiAuthMechanism.API_KEY,
process.env.TEST_API_KEY
)
})

test('should return Contact result successfully with targetId', async () => {
const contactDTO: ContactDTO = {
targetId: 1,
subject: 'Data Question',
body: 'Please help me understand your data. Thank you!',
fromEmail: '[email protected]'
}

let contactInfo: Contact[] = []
try {
contactInfo = await submitContactInfo.execute(contactDTO)
} catch (error) {
throw new Error('Contact info should be submitted')
} finally {
expect(contactInfo).toBeDefined()
expect(contactInfo[0].fromEmail).toEqual('[email protected]')
expect(contactInfo[0].subject).toEqual(expect.any(String))
expect(contactInfo[0].body).toEqual(expect.any(String))
}
})

test('should return a Contact when targetId is not provided', async () => {
const contactDTO: ContactDTO = {
subject: 'General Inquiry',
body: 'I have a general question.',
fromEmail: '[email protected]'
}

let contactInfo: Contact[] = []
try {
contactInfo = await submitContactInfo.execute(contactDTO)
} catch (error) {
throw new Error('Contact info should be submitted even if target id is missing')
} finally {
expect(contactInfo).toBeDefined()
expect(contactInfo[0].fromEmail).toEqual('[email protected]')
expect(contactInfo[0].subject).toEqual(expect.any(String))
expect(contactInfo[0].body).toEqual(expect.any(String))
}
})

test('should return Contact when contact info is successfully submitted with identifier', async () => {
const test2ContactDTO: ContactDTO = {
identifier: 'root',
subject: 'Data Question',
body: 'Please help me understand your data. Thank you!',
fromEmail: '[email protected]'
}
const contactInfo = await submitContactInfo.execute(test2ContactDTO)

expect(contactInfo).toBeDefined()
expect(contactInfo[0].fromEmail).toEqual(test2ContactDTO.fromEmail)
expect(contactInfo[0].subject).toEqual(expect.any(String))
expect(contactInfo[0].body).toEqual(expect.any(String))
})

test('should return error if the target id is unexisted', async () => {
const contactDTO: ContactDTO = {
targetId: 0,
subject: '',
body: '',
fromEmail: '[email protected]'
}
const expectedError = new WriteError(`[400] Feedback target object not found.`)
await expect(submitContactInfo.execute(contactDTO)).rejects.toThrow(expectedError)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ describe('execute', () => {
})

test('should return all facetable metadata fields', async () => {
let metadataFieldInfos: MetadataFieldInfo[] = null
let metadataFieldInfos: MetadataFieldInfo[] | null = null
try {
metadataFieldInfos = await getAllFacetableMetadataFields.execute()
} catch (error) {
throw new Error('Should not raise an error')
} finally {
expect(metadataFieldInfos.length).toBe(59)
expect(metadataFieldInfos[0].name).toBe('authorName')
expect(metadataFieldInfos[0].displayName).toBe('Author Name')
expect(metadataFieldInfos?.length).toBe(64)
expect(metadataFieldInfos?.[0].name).toBe('authorName')
expect(metadataFieldInfos?.[0].displayName).toBe('Author Name')
}
})
})
6 changes: 3 additions & 3 deletions test/functional/metadataBlocks/GetAllMetadataBlocks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ describe('execute', () => {
})

test('should successfully return metadatablocks', async () => {
let metadataBlocks: MetadataBlock[] = null
let metadataBlocks: MetadataBlock[] | null = null
try {
metadataBlocks = await getAllMetadataBlocks.execute()
} catch (error) {
throw new Error('Should not raise an error')
} finally {
expect(metadataBlocks).not.toBeNull()
expect(metadataBlocks.length).toBe(6)
expect(metadataBlocks[0].metadataFields.title.name).toBe('title')
expect(metadataBlocks?.length).toBe(7)
expect(metadataBlocks?.[0].metadataFields.title.name).toBe('title')
}
})
})
76 changes: 76 additions & 0 deletions test/integration/contact/ContactRepository.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { ContactRepository } from '../../../src/contactInfo/infra/repositories/ContactRepository'
import { ApiConfig, Contact, ContactDTO, WriteError } from '../../../src'
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
import { TestConstants } from '../../testHelpers/TestConstants'

describe('submitContactInfo', () => {
beforeAll(async () => {
ApiConfig.init(
TestConstants.TEST_API_URL,
DataverseApiAuthMechanism.API_KEY,
process.env.TEST_API_KEY
)
})

const testContactDTO: ContactDTO = {
targetId: 1,
subject: 'Data Question',
body: 'Please help me understand your data. Thank you!',
fromEmail: '[email protected]'
}

const test2ContactDTO: ContactDTO = {
identifier: 'root',
subject: 'Data Question',
body: 'Please help me understand your data. Thank you!',
fromEmail: '[email protected]'
}

const sut: ContactRepository = new ContactRepository()

test('should return Contact when contact info is successfully submitted', async () => {
const contactInfo = await sut.submitContactInfo(test2ContactDTO)

expect(contactInfo).toBeDefined()
expect(contactInfo[0].fromEmail).toEqual(testContactDTO.fromEmail)
expect(contactInfo[0].subject).toEqual(expect.any(String))
expect(contactInfo[0].body).toEqual(expect.any(String))
})

test('should return Contact when contact info is successfully submitted if accessed by identifier', async () => {
const contactInfo = await sut.submitContactInfo(testContactDTO)

expect(contactInfo).toBeDefined()
expect(contactInfo[0].fromEmail).toEqual(test2ContactDTO.fromEmail)
expect(contactInfo[0].subject).toEqual(expect.any(String))
expect(contactInfo[0].body).toEqual(expect.any(String))
})

test('should return a Contact when targetId is not provided', async () => {
const contactDTOWithoutTargetId: Partial<ContactDTO> = {
subject: 'General Inquiry',
body: 'I have a general question.',
fromEmail: '[email protected]'
}

const contactInfo: Contact[] = await sut.submitContactInfo(
contactDTOWithoutTargetId as ContactDTO
)

expect(contactInfo).toBeDefined()
expect(contactInfo[0].fromEmail).toEqual(contactDTOWithoutTargetId.fromEmail)
expect(contactInfo[0].subject).toEqual(expect.any(String))
expect(contactInfo[0].body).toEqual(expect.any(String))
})

test('should return error if the target id is unexisted', async () => {
const invalidContactDTO: ContactDTO = {
targetId: 0,
subject: '',
body: '',
fromEmail: ''
}
const expectedError = new WriteError(`[400] Feedback target object not found.`)
await expect(sut.submitContactInfo(invalidContactDTO)).rejects.toThrow(expectedError)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('MetadataBlocksRepository', () => {
describe('getAllMetadataBlocks', () => {
test('should return all metadata blocks', async () => {
const actual = await sut.getAllMetadataBlocks()
expect(actual.length).toBe(6)
expect(actual.length).toBe(7)
expect(actual[0].name).toBe(citationMetadataBlockName)
expect(actual[0].metadataFields.title.name).toBe('title')
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('getAllFacetableMetadataFields', () => {
test('should return all facetable metadata fields', async () => {
const actual = await sut.getAllFacetableMetadataFields()

expect(actual.length).toBe(59)
expect(actual.length).toBe(64)
expect(actual[0].name).toBe('authorName')
expect(actual[0].displayName).toBe('Author Name')
})
Expand Down
Loading