Skip to content

Commit

Permalink
MCR-2541: CMS notified when a state uploads response (#2102)
Browse files Browse the repository at this point in the history
* send Q&A response emailer function and template.

* Add Q&A helpers.

* Remove console log.

* Use helpers to remove duplicate code.

* email notification technical doc.

* Change insertQuestionResponse to insert from question via nested writes. Update

* Fix update graphQL types to return entire question when creating response.

* Update apollo cache for create response mutation and add comments on reason for manual cache update.

* Send new response email in resolver.

* Some merge fixes and updating tests.

* Update error message.

* Move getQuestionRound and add and put tests in describe blocks.

* Add helpers for mock Q&A data.

* Send new response email tests.

* Use mock helpers

* cypress re-run

* Update documentation

* Fix test name
  • Loading branch information
JasonLin0991 authored Dec 8, 2023
1 parent e41bb20 commit 4062b6c
Show file tree
Hide file tree
Showing 29 changed files with 1,008 additions and 272 deletions.
99 changes: 99 additions & 0 deletions docs/technical-design/email-notifications.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Email Notifications

Certain contract actions in the MC-Review app will initiate email notifications sent to the State and CMS. These actions include:
- New contract submission
- Unlocking a submitted contract
- Resubmitting an unlocked contract
- New Q&A question submitted
- New Q&A response submitted

The email recipients for each action vary depending on contract data, such as contract type, the state the contract applies to, and the designated state contacts for the contract. The CMS email recipients for contract submission, unlock, and resubmission are generated using the helper function `generateCMSReviewerEmails`.

### New contract submission
- #### State email receivers:
- **stateContacts**: All contacts in the latest submitted contract revision's `formData.stateContacts`.
- **submitterEmails**: User emails that submitted/resubmitted the contract. In resolvers, `submitterEmails` is generated by `contractSubmitters` function that loops through revisions and adding submitInfo.updatedBy emails to a string array.
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- #### CMS email receivers:
- `CONTRACT_ONLY` submissions
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
- **dmcpEmails**: DMCP primary inbox from parameter store `/configuration/email/dmcp`.
- `CONTRACT_AND_RATES` submissions
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
- **dmcpEmails**: DMCP primary inbox from parameter store `/configuration/email/dmcp`.
- **oactEmails**: OACT primary inbox from parameter store `/configuration/email/oact`.
- `CHIP` and State of `PR` submissions
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
### Unlocking a submitted contract
- #### State email receivers:
- **stateContacts**: All contacts in the latest submitted contract revision's `formData.stateContacts`.
- **submitterEmails**: User emails that submitted/resubmitted the contract. In resolvers, `submitterEmails` is generated by `contractSubmitters` function that loops through revisions and adding submitInfo.updatedBy emails to a string array.
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- #### CMS email receivers:
- `CONTRACT_ONLY` submissions
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
- **dmcpEmails**: DMCP primary inbox from parameter store `/configuration/email/dmcp`.
- `CONTRACT_AND_RATES` submissions
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
- **dmcpEmails**: DMCP primary inbox from parameter store `/configuration/email/dmcp`.
- **oactEmails**: OACT primary inbox from parameter store `/configuration/email/oact`.
- `CHIP` and State of `PR` submissions
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
### Resubmitting an unlocked contract
- #### State email receivers:
- **stateContacts**: All contacts in the latest submitted contract revision's `formData.stateContacts`.
- **submitterEmails**: User emails that submitted/resubmitted the contract. In resolvers, `submitterEmails` is generated by `contractSubmitters` function that loops through revisions and adding submitInfo.updatedBy emails to a string array.
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- #### CMS email receivers:
- `CONTRACT_ONLY` submissions
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
- **dmcpEmails**: DMCP primary inbox from parameter store `/configuration/email/dmcp`.
- `CONTRACT_AND_RATES` submissions
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
- **dmcpEmails**: DMCP primary inbox from parameter store `/configuration/email/dmcp`.
- **oactEmails**: OACT primary inbox from parameter store `/configuration/email/oact`.
- `CHIP` and State of `PR` submissions
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
### New Q&A question submitted
- #### State email receivers:
- **stateContacts**: All contacts in the latest submitted contract revision's `formData.stateContacts`.
- **submitterEmails**: User emails that submitted/resubmitted the contract. In resolvers, `submitterEmails` is generated by `contractSubmitters` function that loops through revisions and adding submitInfo.updatedBy emails to a string array.
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- #### CMS email receivers:
- Questions from `DMCO`
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
- Questions from `DMCP`
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
- **dmcpReviewEmails**: DMCP inbox for external communication and Q&A notifications `/configuration/email/dmcpReview`.
- Questions from `OACT`
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
- **oactEmails**: OACT primary inbox from parameter store `/configuration/email/oact`.
### New Q&A response submitted
- #### State email receivers:
- **stateContacts**: All contacts in the latest submitted contract revision's `formData.stateContacts`.
- **submitterEmails**: User emails that submitted/resubmitted the contract. In resolvers, `submitterEmails` is generated by `contractSubmitters` function that loops through revisions and adding submitInfo.updatedBy emails to a string array.
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- #### CMS email receivers:
- Responses to Questions from `DMCO`
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
- Responses to Questions from `DMCP`
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
- **dmcpReviewEmails**: DMCP inbox for external communication and Q&A notifications `/configuration/email/dmcpReview`.
- Responses to Questions from `OACT`
- **devReviewTeamEmails**: Dev team email address from parameter store `/configuration/email/reviewTeamAddresses`.
- **stateAnalystsEmails**: specific state analyst emails from parameter store `/configuration/[STATE]/stateanalysts/email`.
- **oactEmails**: OACT primary inbox from parameter store `/configuration/email/oact`.
103 changes: 66 additions & 37 deletions services/app-api/src/emailer/emailer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
resubmitPackageCMSEmail,
sendQuestionStateEmail,
sendQuestionCMSEmail,
sendQuestionResponseCMSEmail,
} from './'
import type {
LockedHealthPlanFormDataType,
Expand Down Expand Up @@ -91,6 +92,18 @@ type Emailer = {
statePrograms: ProgramType[],
submitterEmails: string[]
) => Promise<void | Error>
sendResubmittedStateEmail: (
formData: LockedHealthPlanFormDataType,
updateInfo: UpdateInfoType,
submitterEmails: string[],
statePrograms: ProgramType[]
) => Promise<void | Error>
sendResubmittedCMSEmail: (
formData: LockedHealthPlanFormDataType,
updateInfo: UpdateInfoType,
stateAnalystsEmails: StateAnalystsEmails,
statePrograms: ProgramType[]
) => Promise<void | Error>
sendQuestionsStateEmail: (
contract: ContractRevisionWithRatesType,
submitterEmails: string[],
Expand All @@ -103,17 +116,12 @@ type Emailer = {
statePrograms: ProgramType[],
questions: Question[]
) => Promise<void | Error>
sendResubmittedStateEmail: (
formData: LockedHealthPlanFormDataType,
updateInfo: UpdateInfoType,
submitterEmails: string[],
statePrograms: ProgramType[]
) => Promise<void | Error>
sendResubmittedCMSEmail: (
formData: LockedHealthPlanFormDataType,
updateInfo: UpdateInfoType,
sendQuestionResponseCMSEmail: (
contractRevision: ContractRevisionWithRatesType,
statePrograms: ProgramType[],
stateAnalystsEmails: StateAnalystsEmails,
statePrograms: ProgramType[]
currentQuestion: Question,
allContractQuestions: Question[]
) => Promise<void | Error>
}
const localEmailerLogger = (emailData: EmailData) =>
Expand Down Expand Up @@ -203,6 +211,44 @@ function emailer(
return await this.sendEmail(emailData)
}
},
sendResubmittedStateEmail: async function (
formData,
updateInfo,
submitterEmails,
statePrograms
) {
const emailData = await resubmitPackageStateEmail(
formData,
submitterEmails,
updateInfo,
config,
statePrograms
)
if (emailData instanceof Error) {
return emailData
} else {
return await this.sendEmail(emailData)
}
},
sendResubmittedCMSEmail: async function (
formData,
updateInfo,
stateAnalystsEmails,
statePrograms
) {
const emailData = await resubmitPackageCMSEmail(
formData,
updateInfo,
config,
stateAnalystsEmails,
statePrograms
)
if (emailData instanceof Error) {
return emailData
} else {
return await this.sendEmail(emailData)
}
},
sendQuestionsStateEmail: async function (
contract,
submitterEmails,
Expand Down Expand Up @@ -241,37 +287,20 @@ function emailer(
return await this.sendEmail(emailData)
}
},
sendResubmittedStateEmail: async function (
formData,
updateInfo,
submitterEmails,
statePrograms
) {
const emailData = await resubmitPackageStateEmail(
formData,
submitterEmails,
updateInfo,
config,
statePrograms
)
if (emailData instanceof Error) {
return emailData
} else {
return await this.sendEmail(emailData)
}
},
sendResubmittedCMSEmail: async function (
formData,
updateInfo,
sendQuestionResponseCMSEmail: async function (
contractRevision,
statePrograms,
stateAnalystsEmails,
statePrograms
currentQuestion,
allContractQuestions
) {
const emailData = await resubmitPackageCMSEmail(
formData,
updateInfo,
const emailData = await sendQuestionResponseCMSEmail(
contractRevision,
config,
statePrograms,
stateAnalystsEmails,
statePrograms
currentQuestion,
allContractQuestions
)
if (emailData instanceof Error) {
return emailData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ exports[`renders overall email for a new question as expected 1`] = `
<b>Round:</b> 1<br />
<b>Date:</b> 01/01/2024<br />
<br />
<a href="http://localhost/submissions/12345/question-and-answers">View submission Q&A</a>"
<a href="http://localhost/submissions/12345/question-and-answers">View submission Q&A</a>
"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders overall CMS email for a new state response as expected 1`] = `
"The state submitted responses to OACT's questions about MCR-MN-0003-SNBC<br />
<b>Submitted by:</b> James Brown <a href="james@example.com">james@example.com</a><br />
<b>Round:</b> 2<br />
<b>Questions sent on:</b> 02/03/2024<br />
<br />
<a href="http://localhost/submissions/12345/question-and-answers">View submission Q&A</a>
"
`;
1 change: 1 addition & 0 deletions services/app-api/src/emailer/emails/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { resubmitPackageCMSEmail } from './resubmitPackageCMSEmail'
export { resubmitPackageStateEmail } from './resubmitPackageStateEmail'
export { sendQuestionStateEmail } from './sendQuestionStateEmail'
export { sendQuestionCMSEmail } from './sendQuestionCMSEmail'
export { sendQuestionResponseCMSEmail } from './sendQuestionResponseCMSEmail'
17 changes: 6 additions & 11 deletions services/app-api/src/emailer/emails/sendQuestionCMSEmail.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import {
testEmailConfig,
mockContractRev,
mockMNState,
mockQuestionAndResponses,
} from '../../testHelpers/emailerHelpers'
import type { CMSUserType, StateType, Question } from '../../domain-models'
import { packageName } from 'app-web/src/common-code/healthPlanFormDataType'
import { sendQuestionCMSEmail } from './index'
import { getTestStateAnalystsEmails } from '../../testHelpers/parameterStoreHelpers'

const stateAnalysts = [
'stateAnalysts1@example.com',
'stateAnalysts1@example.com',
]
const stateAnalysts = getTestStateAnalystsEmails('FL')

const flState: StateType = {
stateCode: 'FL',
Expand All @@ -28,15 +27,11 @@ const cmsUser: CMSUserType = {
}

const questions: Question[] = [
{
id: '1234',
contractID: 'contract-id-test',
createdAt: new Date('01/01/2024'),
mockQuestionAndResponses({
id: 'test-question-id-1',
addedBy: cmsUser,
documents: [],
division: 'DMCO',
responses: [],
},
}),
]

test('to addresses list only includes state analyst when a DMCO user submits a question', async () => {
Expand Down
11 changes: 7 additions & 4 deletions services/app-api/src/emailer/emails/sendQuestionCMSEmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
stripHTMLFromTemplate,
renderTemplate,
findContractPrograms,
getQuestionRound,
} from '../templateHelpers'
import { submissionQuestionResponseURL } from '../generateURLs'
import type { ContractRevisionWithRatesType } from '../../domain-models/contractAndRates'
Expand Down Expand Up @@ -44,9 +45,11 @@ export const sendQuestionCMSEmail = async (
contractRev.contract.id,
config.baseUrl
)
const roundNumber = questions.filter(
(question) => question.division === newQuestion.division
).length
const questionRound = getQuestionRound(questions, newQuestion)

if (questionRound instanceof Error) {
return questionRound
}

const data = {
packageName,
Expand All @@ -55,7 +58,7 @@ export const sendQuestionCMSEmail = async (
cmsRequestorName: `${newQuestion.addedBy.givenName} ${newQuestion.addedBy.familyName}`,
cmsRequestorDivision: newQuestion.addedBy.divisionAssignment,
dateAsked: formatCalendarDate(newQuestion.createdAt),
roundNumber,
questionRound,
}

const result = await renderTemplate<typeof data>(
Expand Down
Loading

0 comments on commit 4062b6c

Please sign in to comment.