Skip to content

Commit

Permalink
[MCR-2538] States are notified when CMS uploads questions about a sub…
Browse files Browse the repository at this point in the history
…mission (#2068)

* Adjust questionResponse resolver to pass needed data to emailer, created base emailer function

* grab additional data in createQuestion resolver to pass to emailer

* rename emailer function

* add testing for new question email notification

* Add emailer to config for createQuestionResolver

* fix api test and add unit test for creatQuestion resolver

* code clean up

* Add failure case

* remove old snapshot

* re run all jobs

* use a relative path import

* change function name newQuestionStateEmail -> sendQuestionStateEmail

* PR fixes for using contractwithrates instead of hpp

* cypress re-run

* add period to sentence in email, refactor sesemailer

* throw error in test

* throw errors in test

* throw errors in test

* WIP: refactor emailer

* update test emailer to fix api test

* rerun all jobs

* re-run cypress

---------

Co-authored-by: Mojo Talantikite <mojo.talantikite@gmail.com>
  • Loading branch information
pearl-truss and mojotalantikite authored Nov 30, 2023
1 parent 5c242c2 commit 869f71e
Show file tree
Hide file tree
Showing 25 changed files with 926 additions and 421 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { z } from 'zod'
import { contractRevisionWithRatesSchema } from './revisionTypes'
import { statusSchema } from './statusType'
import { pruneDuplicateEmails } from '../../emailer/formatters'

// Contract represents the contract specific information in a submission package
// All that data is contained in revisions, each revision represents the data in a single submission
Expand All @@ -26,6 +27,22 @@ const draftContractSchema = contractSchema.extend({
type ContractType = z.infer<typeof contractSchema>
type DraftContractType = z.infer<typeof draftContractSchema>

export { contractRevisionWithRatesSchema, draftContractSchema, contractSchema }
function contractSubmitters(contract: ContractType): string[] {
const submitters: string[] = []
contract.revisions.forEach(
(revision) =>
revision.submitInfo?.updatedBy &&
submitters.push(revision.submitInfo?.updatedBy)
)

return pruneDuplicateEmails(submitters)
}

export {
contractRevisionWithRatesSchema,
draftContractSchema,
contractSchema,
contractSubmitters,
}

export type { ContractType, DraftContractType }
6 changes: 5 additions & 1 deletion services/app-api/src/domain-models/contractAndRates/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
export { rateSchema, draftRateSchema } from './rateTypes'

export { contractSchema, draftContractSchema } from './contractTypes'
export {
contractSchema,
draftContractSchema,
contractSubmitters,
} from './contractTypes'

export { contractFormDataSchema, rateFormDataSchema } from './formDataTypes'

Expand Down
1 change: 1 addition & 0 deletions services/app-api/src/domain-models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export {
export {
convertContractWithRatesRevtoHPPRev,
convertContractWithRatesToUnlockedHPP,
contractSubmitters,
} from './contractAndRates'

export type {
Expand Down
219 changes: 75 additions & 144 deletions services/app-api/src/emailer/emailer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@ import {
unlockPackageStateEmail,
resubmitPackageStateEmail,
resubmitPackageCMSEmail,
sendQuestionStateEmail,
} from './'
import type {
LockedHealthPlanFormDataType,
UnlockedHealthPlanFormDataType,
} from '../../../app-web/src/common-code/healthPlanFormDataType'
import type { UpdateInfoType, ProgramType } from '../domain-models'
import type {
UpdateInfoType,
ProgramType,
CMSUserType,
ContractRevisionWithRatesType,
} from '../domain-models'
import { SESServiceException } from '@aws-sdk/client-ses'

// See more discussion of configuration in docs/Configuration.md
Expand Down Expand Up @@ -56,9 +62,11 @@ type EmailData = {
bodyHTML?: string
}

type SendEmailFunction = (emailData: EmailData) => Promise<void | Error>

type Emailer = {
config: EmailConfiguration
sendEmail: (emailData: EmailData) => Promise<void | Error>
sendEmail: SendEmailFunction
sendCMSNewPackage: (
formData: LockedHealthPlanFormDataType,
stateAnalystsEmails: StateAnalystsEmails,
Expand All @@ -81,6 +89,13 @@ type Emailer = {
statePrograms: ProgramType[],
submitterEmails: string[]
) => Promise<void | Error>
sendQuestionsStateEmail: (
contract: ContractRevisionWithRatesType,
cmsRequesor: CMSUserType,
submitterEmails: string[],
statePrograms: ProgramType[],
dateAsked: Date
) => Promise<void | Error>
sendResubmittedStateEmail: (
formData: LockedHealthPlanFormDataType,
updateInfo: UpdateInfoType,
Expand All @@ -94,26 +109,21 @@ type Emailer = {
statePrograms: ProgramType[]
) => Promise<void | Error>
}
const localEmailerLogger = (emailData: EmailData) =>
console.info(`
EMAIL SENT
${'(¯`·.¸¸.·´¯`·.¸¸.·´¯·.¸¸.·´¯`·.¸¸.·´¯`·.¸¸.·´¯`·.¸¸.·´)'}
${JSON.stringify(getSESEmailParams(emailData))}
${'(¯`·.¸¸.·´¯`·.¸¸.·´¯·.¸¸.·´¯`·.¸¸.·´¯`·.¸¸.·´¯`·.¸¸.·´)'}
`)

function newSESEmailer(config: EmailConfiguration): Emailer {
function emailer(
config: EmailConfiguration,
sendEmail: SendEmailFunction
): Emailer {
return {
config,
sendEmail: async (emailData: EmailData): Promise<void | Error> => {
const emailRequestParams = getSESEmailParams(emailData)

try {
await sendSESEmail(emailRequestParams)
return
} catch (err) {
if (err instanceof SESServiceException) {
return new Error(
'SES email send failed. Error: ' + JSON.stringify(err)
)
}

return new Error('SES email send failed. Error: ' + err)
}
},
sendEmail,
sendCMSNewPackage: async function (
formData,
stateAnalystsEmails,
Expand Down Expand Up @@ -186,141 +196,33 @@ function newSESEmailer(config: EmailConfiguration): Emailer {
return await this.sendEmail(emailData)
}
},
sendResubmittedStateEmail: async function (
formData,
updateInfo,
sendQuestionsStateEmail: async function (
contract,
cmsRequestor,
submitterEmails,
statePrograms
statePrograms,
dateAsked
) {
const emailData = await resubmitPackageStateEmail(
formData,
const emailData = await sendQuestionStateEmail(
contract,
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)
}
},
}
}

const localEmailerLogger = (emailData: EmailData) =>
console.info(`
EMAIL SENT
${'(¯`·.¸¸.·´¯`·.¸¸.·´¯·.¸¸.·´¯`·.¸¸.·´¯`·.¸¸.·´¯`·.¸¸.·´)'}
${JSON.stringify(getSESEmailParams(emailData))}
${'(¯`·.¸¸.·´¯`·.¸¸.·´¯·.¸¸.·´¯`·.¸¸.·´¯`·.¸¸.·´¯`·.¸¸.·´)'}
`)

function newLocalEmailer(config: EmailConfiguration): Emailer {
return {
config,
sendEmail: async (emailData: EmailData): Promise<void | Error> => {
localEmailerLogger(emailData)
},
sendCMSNewPackage: async (
formData,
stateAnalystsEmails,
statePrograms
) => {
const result = await newPackageCMSEmail(
formData,
config,
stateAnalystsEmails,
statePrograms
)
if (result instanceof Error) {
console.error(result)
return result
} else {
localEmailerLogger(result)
}
},
sendStateNewPackage: async (
formData,
submitterEmails,
statePrograms
) => {
const result = await newPackageStateEmail(
formData,
submitterEmails,
config,
statePrograms
)
if (result instanceof Error) {
console.error(result)
return result
} else {
localEmailerLogger(result)
}
},
sendUnlockPackageCMSEmail: async (
formData,
updateInfo,
stateAnalystsEmails,
statePrograms
) => {
const emailData = await unlockPackageCMSEmail(
formData,
updateInfo,
config,
stateAnalystsEmails,
statePrograms
)
if (emailData instanceof Error) {
return emailData
} else {
localEmailerLogger(emailData)
}
},
sendUnlockPackageStateEmail: async (
formData,
updateInfo,
statePrograms,
submitterEmails
) => {
const emailData = await unlockPackageStateEmail(
formData,
updateInfo,
cmsRequestor,
config,
statePrograms,
submitterEmails
dateAsked
)
if (emailData instanceof Error) {
return emailData
} else {
localEmailerLogger(emailData)
return await this.sendEmail(emailData)
}
},
sendResubmittedStateEmail: async (
sendResubmittedStateEmail: async function (
formData,
updateInfo,
submitterEmails,
statePrograms
) => {
) {
const emailData = await resubmitPackageStateEmail(
formData,
submitterEmails,
Expand All @@ -331,15 +233,15 @@ function newLocalEmailer(config: EmailConfiguration): Emailer {
if (emailData instanceof Error) {
return emailData
} else {
localEmailerLogger(emailData)
return await this.sendEmail(emailData)
}
},
sendResubmittedCMSEmail: async (
sendResubmittedCMSEmail: async function (
formData,
updateInfo,
stateAnalystsEmails,
statePrograms
) => {
) {
const emailData = await resubmitPackageCMSEmail(
formData,
updateInfo,
Expand All @@ -350,11 +252,40 @@ function newLocalEmailer(config: EmailConfiguration): Emailer {
if (emailData instanceof Error) {
return emailData
} else {
localEmailerLogger(emailData)
return await this.sendEmail(emailData)
}
},
}
}

export { newLocalEmailer, newSESEmailer }
const sendSESEmails = async (emailData: EmailData): Promise<void | Error> => {
const emailRequestParams = getSESEmailParams(emailData)

try {
await sendSESEmail(emailRequestParams)
return
} catch (err) {
if (err instanceof SESServiceException) {
return new Error(
'SES email send failed. Error: ' + JSON.stringify(err)
)
}

return new Error('SES email send failed. Error: ' + err)
}
}

function newSESEmailer(config: EmailConfiguration): Emailer {
return emailer(config, sendSESEmails)
}

const sendLocalEmails = async (emailData: EmailData): Promise<void | Error> => {
localEmailerLogger(emailData)
}

function newLocalEmailer(config: EmailConfiguration): Emailer {
return emailer(config, sendLocalEmails)
}

export { newLocalEmailer, newSESEmailer, emailer }
export type { Emailer, EmailConfiguration, EmailData, StateAnalystsEmails }
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders overall email for a new question as expected 1`] = `
"CMS asked questions about MCR-MN-0003-SNBC<br />
<b>Sent by:</b> Ronald McDonald (DMCO) <a href="cms@email.com">cms@email.com</a>
<br />
<b>Date:</b> 01/01/2024<br />
<br />
You must answer the question before CMS can continue reviewing it.<br />
<br />
<a href="http://localhost/submissions/12345">Open the submission in MC-Review to answer questions</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 @@ -4,3 +4,4 @@ export { unlockPackageCMSEmail } from './unlockPackageCMSEmail'
export { unlockPackageStateEmail } from './unlockPackageStateEmail'
export { resubmitPackageCMSEmail } from './resubmitPackageCMSEmail'
export { resubmitPackageStateEmail } from './resubmitPackageStateEmail'
export { sendQuestionStateEmail } from './sendQuestionStateEmail'
Loading

0 comments on commit 869f71e

Please sign in to comment.