diff --git a/docs/Configuration.md b/docs/Configuration.md index caee859e83..0136eaf640 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -211,9 +211,14 @@ We plan to move this to the DB down the road. Until then, know that if these val *[same in prod/val]* This the help address displayed in state emails for contacting the analyst review team. -#### `/configuration/email/dmcp` +#### `/configuration/email/dmcpSubmission` + +*[environment specific]* This contains the DMCP primary inbox. The DMCP team is focused on policy issues related to managed care. They review all submissions, excluding CHIP and state of PR. This inbox is also primarily used for internal communication between DMCP, OACT, and DMCO. + +#### `/configuration/email/dmcpReview` + +*[environment specific]* This contains the DMCP inbox for external communication and Q&A notifications. -*[environment specific]* This contains the DMCP primary inbox. The DMCP team is focused on policy issues related to managed care. They review all submissions, excluding CHIP and state of PR. #### `/configuration/email/oact` @@ -223,6 +228,7 @@ We plan to move this to the DB down the road. Until then, know that if these val *[environment specific]* This contains the DMCO primary inbox. The DMCO team is focused on managed care contracts and they review all submissions. + #### `/configuration/email/reviewTeamAddresses` *[environment specific]* List of emails for dev teams/individuals that want to follow all emails. In prod, this is two addresses associated with MC-Review dev team. diff --git a/services/app-api/src/emailer/emailer.ts b/services/app-api/src/emailer/emailer.ts index f451551efa..27e58e3d88 100644 --- a/services/app-api/src/emailer/emailer.ts +++ b/services/app-api/src/emailer/emailer.ts @@ -9,6 +9,7 @@ import { resubmitPackageStateEmail, resubmitPackageCMSEmail, sendQuestionStateEmail, + sendQuestionCMSEmail, } from './' import type { LockedHealthPlanFormDataType, @@ -17,8 +18,8 @@ import type { import type { UpdateInfoType, ProgramType, - CMSUserType, ContractRevisionWithRatesType, + Question, } from '../domain-models' import { SESServiceException } from '@aws-sdk/client-ses' @@ -36,7 +37,8 @@ type EmailConfiguration = { */ devReviewTeamEmails: string[] // added by default to all incoming submissions oactEmails: string[] // OACT division emails - dmcpEmails: string[] // DMCP division emails + dmcpReviewEmails: string[] // DMCP division emails for reviews + dmcpSubmissionEmails: string[] // DMCP division emails for submissions dmcoEmails: string[] // DMCO division emails /* Email addresses used in display text @@ -91,10 +93,15 @@ type Emailer = { ) => Promise sendQuestionsStateEmail: ( contract: ContractRevisionWithRatesType, - cmsRequesor: CMSUserType, submitterEmails: string[], statePrograms: ProgramType[], - dateAsked: Date + questions: Question[] + ) => Promise + sendQuestionsCMSEmail: ( + contract: ContractRevisionWithRatesType, + stateAnalystsEmails: StateAnalystsEmails, + statePrograms: ProgramType[], + questions: Question[] ) => Promise sendResubmittedStateEmail: ( formData: LockedHealthPlanFormDataType, @@ -198,18 +205,35 @@ function emailer( }, sendQuestionsStateEmail: async function ( contract, - cmsRequestor, submitterEmails, statePrograms, - dateAsked + questions ) { const emailData = await sendQuestionStateEmail( contract, submitterEmails, - cmsRequestor, config, statePrograms, - dateAsked + questions + ) + if (emailData instanceof Error) { + return emailData + } else { + return await this.sendEmail(emailData) + } + }, + sendQuestionsCMSEmail: async function ( + contract, + stateAnalystsEmails, + statePrograms, + questions + ) { + const emailData = await sendQuestionCMSEmail( + contract, + stateAnalystsEmails, + config, + statePrograms, + questions ) if (emailData instanceof Error) { return emailData diff --git a/services/app-api/src/emailer/emails/__snapshots__/sendQuestionCMSEmail.test.ts.snap b/services/app-api/src/emailer/emails/__snapshots__/sendQuestionCMSEmail.test.ts.snap new file mode 100644 index 0000000000..db97dd1212 --- /dev/null +++ b/services/app-api/src/emailer/emails/__snapshots__/sendQuestionCMSEmail.test.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders overall email for a new question as expected 1`] = ` +"DMCO sent questions to the state for submission MCR-MN-0003-SNBC
+Sent by: Ronald McDonald (DMCO) cms@email.com +
+Round: 1
+Date: 01/01/2024
+
+View submission Q&A" +`; diff --git a/services/app-api/src/emailer/emails/__snapshots__/sendQuestionStateEmail.test.ts.snap b/services/app-api/src/emailer/emails/__snapshots__/sendQuestionStateEmail.test.ts.snap index 4dfa19cc1d..6960507fe4 100644 --- a/services/app-api/src/emailer/emails/__snapshots__/sendQuestionStateEmail.test.ts.snap +++ b/services/app-api/src/emailer/emails/__snapshots__/sendQuestionStateEmail.test.ts.snap @@ -8,7 +8,7 @@ exports[`renders overall email for a new question as expected 1`] = `
You must answer the question before CMS can continue reviewing it.

-Open the submission in MC-Review to answer questions +Open the submission in MC-Review to answer questions " `; diff --git a/services/app-api/src/emailer/emails/index.ts b/services/app-api/src/emailer/emails/index.ts index 7010b2ea89..c7628c3d5e 100644 --- a/services/app-api/src/emailer/emails/index.ts +++ b/services/app-api/src/emailer/emails/index.ts @@ -5,3 +5,4 @@ export { unlockPackageStateEmail } from './unlockPackageStateEmail' export { resubmitPackageCMSEmail } from './resubmitPackageCMSEmail' export { resubmitPackageStateEmail } from './resubmitPackageStateEmail' export { sendQuestionStateEmail } from './sendQuestionStateEmail' +export { sendQuestionCMSEmail } from './sendQuestionCMSEmail' diff --git a/services/app-api/src/emailer/emails/newPackageCMSEmail.test.ts b/services/app-api/src/emailer/emails/newPackageCMSEmail.test.ts index 99a6c265a7..cf3088de79 100644 --- a/services/app-api/src/emailer/emails/newPackageCMSEmail.test.ts +++ b/services/app-api/src/emailer/emails/newPackageCMSEmail.test.ts @@ -61,7 +61,7 @@ test('to addresses list includes OACT and DMCP group emails for contract and rat ) }) - testEmailConfig().dmcpEmails.forEach((emailAddress) => { + testEmailConfig().dmcpSubmissionEmails.forEach((emailAddress) => { expect(template).toEqual( expect.objectContaining({ toAddresses: expect.arrayContaining([emailAddress]), @@ -101,7 +101,7 @@ test('to addresses list does not include OACT and DMCP group emails for CHIP su ) }) - testEmailConfig().dmcpEmails.forEach((emailAddress) => { + testEmailConfig().dmcpSubmissionEmails.forEach((emailAddress) => { expect(template).not.toEqual( expect.objectContaining({ toAddresses: expect.arrayContaining([emailAddress]), diff --git a/services/app-api/src/emailer/emails/resubmitPackageCMSEmail.test.ts b/services/app-api/src/emailer/emails/resubmitPackageCMSEmail.test.ts index e8dc72c647..f8a78b5a11 100644 --- a/services/app-api/src/emailer/emails/resubmitPackageCMSEmail.test.ts +++ b/services/app-api/src/emailer/emails/resubmitPackageCMSEmail.test.ts @@ -320,7 +320,7 @@ describe('with rates', () => { throw template } - testEmailConfig().dmcpEmails.forEach((emailAddress) => { + testEmailConfig().dmcpSubmissionEmails.forEach((emailAddress) => { expect(template).toEqual( expect.objectContaining({ toAddresses: expect.arrayContaining([emailAddress]), diff --git a/services/app-api/src/emailer/emails/sendQuestionCMSEmail.test.ts b/services/app-api/src/emailer/emails/sendQuestionCMSEmail.test.ts new file mode 100644 index 0000000000..f9cca51a62 --- /dev/null +++ b/services/app-api/src/emailer/emails/sendQuestionCMSEmail.test.ts @@ -0,0 +1,246 @@ +import { + testEmailConfig, + mockContractRev, + mockMNState, +} from '../../testHelpers/emailerHelpers' +import type { CMSUserType, StateType, Question } from '../../domain-models' +import { packageName } from 'app-web/src/common-code/healthPlanFormDataType' +import { sendQuestionCMSEmail } from './index' + +const stateAnalysts = [ + 'stateAnalysts1@example.com', + 'stateAnalysts1@example.com', +] + +const flState: StateType = { + stateCode: 'FL', + name: 'Florida', +} + +const cmsUser: CMSUserType = { + id: '1234', + role: 'CMS_USER', + divisionAssignment: 'DMCO', + familyName: 'McDonald', + givenName: 'Ronald', + email: 'cms@email.com', + stateAssignments: [flState], +} + +const questions: Question[] = [ + { + id: '1234', + contractID: 'contract-id-test', + createdAt: new Date('01/01/2024'), + addedBy: cmsUser, + documents: [], + division: 'DMCO', + responses: [], + }, +] + +test('to addresses list only includes state analyst when a DMCO user submits a question', async () => { + const sub = mockContractRev() + const defaultStatePrograms = mockMNState().programs + + const template = await sendQuestionCMSEmail( + sub, + stateAnalysts, + testEmailConfig(), + defaultStatePrograms, + questions + ) + + if (template instanceof Error) { + throw template + } + + expect(template).toEqual( + expect.not.objectContaining({ + toAddresses: expect.arrayContaining([ + ...testEmailConfig().oactEmails, + ...testEmailConfig().dmcpReviewEmails, + ]), + }) + ) + + expect(template).toEqual( + expect.objectContaining({ + toAddresses: expect.arrayContaining([...stateAnalysts]), + }) + ) +}) + +test('to addresses list includes state analyst and OACT group emails when an OACT user submits a question', async () => { + const sub = mockContractRev() + const defaultStatePrograms = mockMNState().programs + const oactUser: CMSUserType = { + ...cmsUser, + divisionAssignment: 'OACT', + } + const questionsFromOACT: Question[] = [ + { + ...questions[0], + addedBy: oactUser, + }, + ] + const template = await sendQuestionCMSEmail( + sub, + stateAnalysts, + testEmailConfig(), + defaultStatePrograms, + questionsFromOACT + ) + + if (template instanceof Error) { + throw template + } + + expect(template).toEqual( + expect.objectContaining({ + toAddresses: expect.arrayContaining([ + ...stateAnalysts, + ...testEmailConfig().oactEmails, + ]), + }) + ) +}) + +test('to addresses list includes state analyst and DMCP group emails when a DMCP user submits a question', async () => { + const sub = mockContractRev() + const defaultStatePrograms = mockMNState().programs + const dmcpUser: CMSUserType = { + ...cmsUser, + divisionAssignment: 'DMCP', + } + const questionsFromDMCP: Question[] = [ + { + ...questions[0], + addedBy: dmcpUser, + }, + ] + const template = await sendQuestionCMSEmail( + sub, + stateAnalysts, + testEmailConfig(), + defaultStatePrograms, + questionsFromDMCP + ) + + if (template instanceof Error) { + throw template + } + + expect(template).toEqual( + expect.objectContaining({ + toAddresses: expect.arrayContaining([ + ...stateAnalysts, + ...testEmailConfig().dmcpReviewEmails, + ]), + }) + ) +}) + +test('subject line is correct', async () => { + const sub = mockContractRev() + const defaultStatePrograms = mockMNState().programs + const name = packageName( + sub.contract.stateCode, + sub.contract.stateNumber, + sub.formData.programIDs, + defaultStatePrograms + ) + + const template = await sendQuestionCMSEmail( + sub, + stateAnalysts, + testEmailConfig(), + defaultStatePrograms, + questions + ) + + if (template instanceof Error) { + throw template + } + + expect(template).toEqual( + expect.objectContaining({ + subject: expect.stringContaining(`Questions sent for ${name}`), + bodyText: expect.stringContaining(`${name}`), + }) + ) +}) + +test('includes link to the question response page', async () => { + const sub = mockContractRev() + const defaultStatePrograms = mockMNState().programs + const template = await sendQuestionCMSEmail( + sub, + stateAnalysts, + testEmailConfig(), + defaultStatePrograms, + questions + ) + + if (template instanceof Error) { + throw template + } + + expect(template).toEqual( + expect.objectContaining({ + bodyText: expect.stringMatching(/View submission Q&A/), + bodyHTML: expect.stringContaining( + `http://localhost/submissions/${sub.contract.id}/question-and-answer` + ), + }) + ) +}) + +test('includes expected data on the CMS analyst who sent the question', async () => { + const sub = mockContractRev() + const defaultStatePrograms = mockMNState().programs + + const template = await sendQuestionCMSEmail( + sub, + stateAnalysts, + testEmailConfig(), + defaultStatePrograms, + questions + ) + + if (template instanceof Error) { + throw template + } + + expect(template).toEqual( + expect.objectContaining({ + bodyText: expect.stringContaining( + 'Sent by: Ronald McDonald (DMCO) cms@email.com (cms@email.com)' + ), + }) + ) + expect(template).toEqual( + expect.objectContaining({ + bodyText: expect.stringContaining('Date: 01/01/2024'), + }) + ) +}) + +test('renders overall email for a new question as expected', async () => { + const sub = mockContractRev() + const defaultStatePrograms = mockMNState().programs + const result = await sendQuestionCMSEmail( + sub, + stateAnalysts, + testEmailConfig(), + defaultStatePrograms, + questions + ) + + if (result instanceof Error) { + console.error(result) + return + } + + expect(result.bodyHTML).toMatchSnapshot() +}) diff --git a/services/app-api/src/emailer/emails/sendQuestionCMSEmail.ts b/services/app-api/src/emailer/emails/sendQuestionCMSEmail.ts new file mode 100644 index 0000000000..d7d1838236 --- /dev/null +++ b/services/app-api/src/emailer/emails/sendQuestionCMSEmail.ts @@ -0,0 +1,80 @@ +import { packageName as generatePackageName } from '../../../../app-web/src/common-code/healthPlanFormDataType' +import { formatCalendarDate } from '../../../../app-web/src/common-code/dateHelpers' +import { pruneDuplicateEmails } from '../formatters' +import type { EmailConfiguration, EmailData, StateAnalystsEmails } from '..' +import type { ProgramType, Question } from '../../domain-models' +import { + stripHTMLFromTemplate, + renderTemplate, + findContractPrograms, +} from '../templateHelpers' +import { submissionQuestionResponseURL } from '../generateURLs' +import type { ContractRevisionWithRatesType } from '../../domain-models/contractAndRates' + +export const sendQuestionCMSEmail = async ( + contractRev: ContractRevisionWithRatesType, + stateAnalystsEmails: StateAnalystsEmails, + config: EmailConfiguration, + statePrograms: ProgramType[], + questions: Question[] +): Promise => { + const newQuestion = questions[questions.length - 1] + let receiverEmails = [...stateAnalystsEmails, ...config.devReviewTeamEmails] + if (newQuestion.addedBy.divisionAssignment === 'DMCP') { + receiverEmails.push(...config.dmcpReviewEmails) + } else if (newQuestion.addedBy.divisionAssignment === 'OACT') { + receiverEmails.push(...config.oactEmails) + } + receiverEmails = pruneDuplicateEmails(receiverEmails) + + //This checks to make sure all programs contained in submission exists for the state. + const packagePrograms = findContractPrograms(contractRev, statePrograms) + if (packagePrograms instanceof Error) { + return packagePrograms + } + + const packageName = generatePackageName( + contractRev.contract.stateCode, + contractRev.contract.stateNumber, + contractRev.formData.programIDs, + packagePrograms + ) + + const questionResponseURL = submissionQuestionResponseURL( + contractRev.contract.id, + config.baseUrl + ) + const roundNumber = questions.filter( + (question) => question.division === newQuestion.division + ).length + + const data = { + packageName, + questionResponseURL, + cmsRequestorEmail: newQuestion.addedBy.email, + cmsRequestorName: `${newQuestion.addedBy.givenName} ${newQuestion.addedBy.familyName}`, + cmsRequestorDivision: newQuestion.addedBy.divisionAssignment, + dateAsked: formatCalendarDate(newQuestion.createdAt), + roundNumber, + } + + const result = await renderTemplate( + 'sendQuestionCMSEmail', + data + ) + + if (result instanceof Error) { + return result + } else { + return { + toAddresses: receiverEmails, + sourceEmail: config.emailSource, + replyToAddresses: [config.helpDeskEmail], + subject: `${ + config.stage !== 'prod' ? `[${config.stage}] ` : '' + }Questions sent for ${packageName}`, + bodyText: stripHTMLFromTemplate(result), + bodyHTML: result, + } + } +} diff --git a/services/app-api/src/emailer/emails/sendQuestionStateEmail.test.ts b/services/app-api/src/emailer/emails/sendQuestionStateEmail.test.ts index 57218eb235..708e40f292 100644 --- a/services/app-api/src/emailer/emails/sendQuestionStateEmail.test.ts +++ b/services/app-api/src/emailer/emails/sendQuestionStateEmail.test.ts @@ -8,7 +8,7 @@ import type { ContractRevisionWithRatesType, StateType, } from '../../domain-models' -import type { ContractFormDataType } from '../../domain-models' +import type { ContractFormDataType, Question } from '../../domain-models' import { packageName } from 'app-web/src/common-code/healthPlanFormDataType' import { sendQuestionStateEmail } from './index' @@ -29,6 +29,18 @@ const cmsUser: CMSUserType = { stateAssignments: [flState], } +const questions: Question[] = [ + { + id: '1234', + contractID: 'contract-id-test', + createdAt: new Date('01/01/2024'), + addedBy: cmsUser, + documents: [], + division: 'DMCO', + responses: [], + }, +] + const formData: ContractFormDataType = { programIDs: ['abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce'], populationCovered: 'CHIP', @@ -87,17 +99,16 @@ const formData: ContractFormDataType = { }, ], } -const dateAsked = new Date('01/01/2024') + test('to addresses list includes submitter emails', async () => { const sub = mockContractRev() const defaultStatePrograms = mockMNState().programs const template = await sendQuestionStateEmail( sub, defaultSubmitters, - cmsUser, testEmailConfig(), defaultStatePrograms, - dateAsked + questions ) if (template instanceof Error) { @@ -119,10 +130,9 @@ test('to addresses list includes all state contacts on submission', async () => const template = await sendQuestionStateEmail( sub, defaultSubmitters, - cmsUser, testEmailConfig(), defaultStatePrograms, - dateAsked + questions ) if (template instanceof Error) { @@ -162,10 +172,9 @@ test('to addresses list does not include duplicate state receiver emails on subm const template = await sendQuestionStateEmail( sub, defaultSubmitters, - cmsUser, testEmailConfig(), defaultStatePrograms, - dateAsked + questions ) if (template instanceof Error) { @@ -192,10 +201,9 @@ test('subject line is correct and clearly states submission is complete', async const template = await sendQuestionStateEmail( sub, defaultSubmitters, - cmsUser, testEmailConfig(), defaultStatePrograms, - dateAsked + questions ) if (template instanceof Error) { @@ -216,10 +224,9 @@ test('includes link to submission', async () => { const template = await sendQuestionStateEmail( sub, defaultSubmitters, - cmsUser, testEmailConfig(), defaultStatePrograms, - dateAsked + questions ) if (template instanceof Error) { @@ -232,7 +239,7 @@ test('includes link to submission', async () => { 'Open the submission in MC-Review to answer questions' ), bodyHTML: expect.stringContaining( - `href="http://localhost/submissions/${sub.contract.id}"` + `http://localhost/submissions/${sub.contract.id}/question-and-answer` ), }) ) @@ -244,10 +251,9 @@ test('includes information about what to do next', async () => { const template = await sendQuestionStateEmail( sub, defaultSubmitters, - cmsUser, testEmailConfig(), defaultStatePrograms, - dateAsked + questions ) if (template instanceof Error) { @@ -270,10 +276,9 @@ test('includes expected data on the CMS analyst who sent the question', async () const template = await sendQuestionStateEmail( sub, defaultSubmitters, - cmsUser, testEmailConfig(), defaultStatePrograms, - dateAsked + questions ) if (template instanceof Error) { @@ -300,10 +305,9 @@ test('renders overall email for a new question as expected', async () => { const result = await sendQuestionStateEmail( sub, defaultSubmitters, - cmsUser, testEmailConfig(), defaultStatePrograms, - dateAsked + questions ) if (result instanceof Error) { diff --git a/services/app-api/src/emailer/emails/sendQuestionStateEmail.ts b/services/app-api/src/emailer/emails/sendQuestionStateEmail.ts index 7a8dedd014..d1a6b1ed52 100644 --- a/services/app-api/src/emailer/emails/sendQuestionStateEmail.ts +++ b/services/app-api/src/emailer/emails/sendQuestionStateEmail.ts @@ -2,24 +2,24 @@ import { packageName as generatePackageName } from '../../../../app-web/src/comm import { formatCalendarDate } from '../../../../app-web/src/common-code/dateHelpers' import { pruneDuplicateEmails } from '../formatters' import type { EmailConfiguration, EmailData } from '..' -import type { ProgramType, CMSUserType } from '../../domain-models' +import type { ProgramType, Question } from '../../domain-models' import { stripHTMLFromTemplate, renderTemplate, findContractPrograms, } from '../templateHelpers' -import { submissionSummaryURL } from '../generateURLs' +import { submissionQuestionResponseURL } from '../generateURLs' import type { ContractRevisionWithRatesType } from '../../domain-models/contractAndRates' export const sendQuestionStateEmail = async ( contractRev: ContractRevisionWithRatesType, submitterEmails: string[], - cmsRequestor: CMSUserType, config: EmailConfiguration, statePrograms: ProgramType[], - dateAsked: Date + questions: Question[] ): Promise => { const stateContactEmails: string[] = [] + const newQuestion = questions[questions.length - 1] contractRev.formData.stateContacts.forEach((contact) => { if (contact.email) stateContactEmails.push(contact.email) @@ -43,18 +43,18 @@ export const sendQuestionStateEmail = async ( packagePrograms ) - const packageURL = submissionSummaryURL( + const questionResponseURL = submissionQuestionResponseURL( contractRev.contract.id, config.baseUrl ) const data = { packageName, - submissionURL: packageURL, - cmsRequestorEmail: cmsRequestor.email, - cmsRequestorName: `${cmsRequestor.givenName} ${cmsRequestor.familyName}`, - cmsRequestorDivision: cmsRequestor.divisionAssignment, - dateAsked: formatCalendarDate(dateAsked), + questionResponseURL: questionResponseURL, + cmsRequestorEmail: newQuestion.addedBy.email, + cmsRequestorName: `${newQuestion.addedBy.givenName} ${newQuestion.addedBy.familyName}`, + cmsRequestorDivision: newQuestion.addedBy.divisionAssignment, + dateAsked: formatCalendarDate(newQuestion.createdAt), } const result = await renderTemplate( diff --git a/services/app-api/src/emailer/emails/unlockPackageCMSEmail.test.ts b/services/app-api/src/emailer/emails/unlockPackageCMSEmail.test.ts index 7647002d6a..dbf2915103 100644 --- a/services/app-api/src/emailer/emails/unlockPackageCMSEmail.test.ts +++ b/services/app-api/src/emailer/emails/unlockPackageCMSEmail.test.ts @@ -329,7 +329,7 @@ describe('unlockPackageCMSEmail', () => { ) }) - testEmailConfig().dmcpEmails.forEach((emailAddress) => { + testEmailConfig().dmcpSubmissionEmails.forEach((emailAddress) => { expect(template).toEqual( expect.objectContaining({ toAddresses: expect.arrayContaining([emailAddress]), diff --git a/services/app-api/src/emailer/etaTemplates/sendQuestionCMSEmail.eta b/services/app-api/src/emailer/etaTemplates/sendQuestionCMSEmail.eta new file mode 100644 index 0000000000..fd394f4ca1 --- /dev/null +++ b/services/app-api/src/emailer/etaTemplates/sendQuestionCMSEmail.eta @@ -0,0 +1,8 @@ +<%= it.cmsRequestorDivision %> sent questions to the state for submission <%= it.packageName %>
+Sent by: <%= it.cmsRequestorName %> (<%= it.cmsRequestorDivision %>) <%= it.cmsRequestorEmail %> +
+Round: <%= it.roundNumber %> +
+Date: <%= it.dateAsked %>
+
+View submission Q&A \ No newline at end of file diff --git a/services/app-api/src/emailer/etaTemplates/sendQuestionStateEmail.eta b/services/app-api/src/emailer/etaTemplates/sendQuestionStateEmail.eta index b1d5905768..c6e90f1bc0 100644 --- a/services/app-api/src/emailer/etaTemplates/sendQuestionStateEmail.eta +++ b/services/app-api/src/emailer/etaTemplates/sendQuestionStateEmail.eta @@ -5,5 +5,5 @@ CMS asked questions about <%= it.packageName %>

You must answer the question before CMS can continue reviewing it.

-Open the submission in MC-Review to answer questions +Open the submission in MC-Review to answer questions diff --git a/services/app-api/src/emailer/generateURLs.ts b/services/app-api/src/emailer/generateURLs.ts index e84284acc0..1e933bd3d5 100644 --- a/services/app-api/src/emailer/generateURLs.ts +++ b/services/app-api/src/emailer/generateURLs.ts @@ -27,4 +27,17 @@ function submissionSummaryURL(id: string, base: string): string { return url } -export { reviewAndSubmitURL, submissionSummaryURL } +function submissionQuestionResponseURL(id: string, base: string): string { + const pattern = RoutesRecord.SUBMISSIONS_QUESTIONS_AND_ANSWERS + const toPath = compile(pattern, { encode: encodeURIComponent }) + const path = toPath({ id }) + const url = new URL(path, base).href + + return url +} + +export { + reviewAndSubmitURL, + submissionSummaryURL, + submissionQuestionResponseURL, +} diff --git a/services/app-api/src/emailer/index.ts b/services/app-api/src/emailer/index.ts index 37cdef3e18..443b7b72c8 100644 --- a/services/app-api/src/emailer/index.ts +++ b/services/app-api/src/emailer/index.ts @@ -8,6 +8,7 @@ export { resubmitPackageStateEmail, resubmitPackageCMSEmail, sendQuestionStateEmail, + sendQuestionCMSEmail, } from './emails' export type { EmailConfiguration, diff --git a/services/app-api/src/emailer/templateHelpers.test.ts b/services/app-api/src/emailer/templateHelpers.test.ts index eeaa3a5910..ceb1aceeb3 100644 --- a/services/app-api/src/emailer/templateHelpers.test.ts +++ b/services/app-api/src/emailer/templateHelpers.test.ts @@ -31,7 +31,7 @@ describe('templateHelpers', () => { expectedResult: [ ...testEmailConfig().devReviewTeamEmails, ...testStateAnalystsEmails, - ...testEmailConfig().dmcpEmails, + ...testEmailConfig().dmcpSubmissionEmails, ], }, { @@ -42,7 +42,7 @@ describe('templateHelpers', () => { expectedResult: [ ...testEmailConfig().devReviewTeamEmails, ...testStateAnalystsEmails, - ...testEmailConfig().dmcpEmails, + ...testEmailConfig().dmcpSubmissionEmails, ...testEmailConfig().oactEmails, ], }, @@ -202,7 +202,7 @@ describe('templateHelpers', () => { reviewers: [ 'Bobloblaw@example.com', 'Lucille.Bluth@example.com', - testEmailConfig().dmcpEmails[0], + testEmailConfig().dmcpSubmissionEmails[0], ], config: testEmailConfig(), testDescription: 'removes dmcp emails', diff --git a/services/app-api/src/emailer/templateHelpers.ts b/services/app-api/src/emailer/templateHelpers.ts index 20a2aad2bf..7493658cb7 100644 --- a/services/app-api/src/emailer/templateHelpers.ts +++ b/services/app-api/src/emailer/templateHelpers.ts @@ -83,17 +83,18 @@ const filterChipAndPRSubmissionReviewers = ( reviewers: string[], config: EmailConfiguration ) => { - const { oactEmails, dmcpEmails } = config + const { oactEmails, dmcpSubmissionEmails } = config return reviewers.filter( - (email) => !dmcpEmails.includes(email) && !oactEmails.includes(email) + (email) => + !dmcpSubmissionEmails.includes(email) && !oactEmails.includes(email) ) } /* Determine reviewers for a given health plan package and state - devReviewTeamEmails added to all emails by default - - dmcpEmails added in both CONTRACT_ONLY and CONTRACT_AND_RATES + - dmcpSubmissionEmails added in both CONTRACT_ONLY and CONTRACT_AND_RATES - oactEmails added for CONTRACT_AND_RATES - dmco is added to emails via state analysts @@ -113,7 +114,7 @@ const generateCMSReviewerEmails = ( ) } - const { oactEmails, dmcpEmails } = config + const { oactEmails, dmcpSubmissionEmails } = config let reviewers: string[] = [] if (pkg.submissionType === 'CONTRACT_ONLY') { @@ -121,14 +122,14 @@ const generateCMSReviewerEmails = ( reviewers = [ ...config.devReviewTeamEmails, ...stateAnalystsEmails, - ...dmcpEmails, + ...dmcpSubmissionEmails, ] } else if (pkg.submissionType === 'CONTRACT_AND_RATES') { //Contract and rate submissions reviewer emails. reviewers = [ ...config.devReviewTeamEmails, ...stateAnalystsEmails, - ...dmcpEmails, + ...dmcpSubmissionEmails, ...oactEmails, ] } diff --git a/services/app-api/src/handlers/apollo_gql.ts b/services/app-api/src/handlers/apollo_gql.ts index 2abf459b38..c707be17ce 100644 --- a/services/app-api/src/handlers/apollo_gql.ts +++ b/services/app-api/src/handlers/apollo_gql.ts @@ -240,7 +240,9 @@ async function initializeGQLHandler(): Promise { const cmsRateHelpEmailAddress = await emailParameterStore.getCmsRateHelpEmail() const oactEmails = await emailParameterStore.getOACTEmails() - const dmcpEmails = await emailParameterStore.getDMCPEmails() + const dmcpReviewEmails = await emailParameterStore.getDMCPReviewEmails() + const dmcpSubmissionEmails = + await emailParameterStore.getDMCPSubmissionEmails() const dmcoEmails = await emailParameterStore.getDMCOEmails() if (emailSource instanceof Error) @@ -267,8 +269,11 @@ async function initializeGQLHandler(): Promise { if (oactEmails instanceof Error) throw new Error(`Configuration Error: ${oactEmails.message}`) - if (dmcpEmails instanceof Error) - throw new Error(`Configuration Error: ${dmcpEmails.message}`) + if (dmcpReviewEmails instanceof Error) + throw new Error(`Configuration Error: ${dmcpReviewEmails.message}`) + + if (dmcpSubmissionEmails instanceof Error) + throw new Error(`Configuration Error: ${dmcpSubmissionEmails.message}`) if (dmcoEmails instanceof Error) throw new Error(`Configuration Error: ${dmcoEmails.message}`) @@ -324,7 +329,8 @@ async function initializeGQLHandler(): Promise { cmsReviewHelpEmailAddress, cmsRateHelpEmailAddress, oactEmails, - dmcpEmails, + dmcpReviewEmails, + dmcpSubmissionEmails, dmcoEmails, helpDeskEmail, }) @@ -336,7 +342,8 @@ async function initializeGQLHandler(): Promise { cmsReviewHelpEmailAddress, cmsRateHelpEmailAddress, oactEmails, - dmcpEmails, + dmcpReviewEmails, + dmcpSubmissionEmails, dmcoEmails, helpDeskEmail, }) diff --git a/services/app-api/src/parameterStore/emailParameterStore/dmcpEmails/getDMCPEmails.ts b/services/app-api/src/parameterStore/emailParameterStore/dmcpEmails/getDMCPReviewEmails.ts similarity index 66% rename from services/app-api/src/parameterStore/emailParameterStore/dmcpEmails/getDMCPEmails.ts rename to services/app-api/src/parameterStore/emailParameterStore/dmcpEmails/getDMCPReviewEmails.ts index 92d46b5f60..9dc81cef99 100644 --- a/services/app-api/src/parameterStore/emailParameterStore/dmcpEmails/getDMCPEmails.ts +++ b/services/app-api/src/parameterStore/emailParameterStore/dmcpEmails/getDMCPReviewEmails.ts @@ -1,13 +1,13 @@ import { ParameterStore } from '../../awsParameterStore' import { validateAndReturnValueArray } from '../helpers' -export const getDMCPEmails = async (): Promise => { - const name = `/configuration/email/dmcp` +export const getDMCPReviewEmails = async (): Promise => { + const name = `/configuration/email/dmcpReview` const dmcpTeamAddresses = await ParameterStore.getParameter(name) return validateAndReturnValueArray(dmcpTeamAddresses, name) } -export const getDMCPEmailsLocal = async (): Promise => [ +export const getDMCPReviewEmailsLocal = async (): Promise => [ `"DMCP Reviewer 1" `, `"DMCP Reviewer 2" `, `"DMCP Reviewer 3" `, diff --git a/services/app-api/src/parameterStore/emailParameterStore/dmcpEmails/getDMCPSubmissionEmails.ts b/services/app-api/src/parameterStore/emailParameterStore/dmcpEmails/getDMCPSubmissionEmails.ts new file mode 100644 index 0000000000..7d81f24475 --- /dev/null +++ b/services/app-api/src/parameterStore/emailParameterStore/dmcpEmails/getDMCPSubmissionEmails.ts @@ -0,0 +1,16 @@ +import { ParameterStore } from '../../awsParameterStore' +import { validateAndReturnValueArray } from '../helpers' + +export const getDMCPSubmissionEmails = async (): Promise => { + const name = `/configuration/email/dmcpSubmission` + const dmcpTeamAddresses = await ParameterStore.getParameter(name) + return validateAndReturnValueArray(dmcpTeamAddresses, name) +} + +export const getDMCPSubmissionEmailsLocal = async (): Promise< + string[] | Error +> => [ + `"DMCP Submission Reviewer 1" `, + `"DMCP Submission Reviewer 2" `, + `"DMCP Submission Reviewer 3" `, +] diff --git a/services/app-api/src/parameterStore/emailParameterStore/emailParameterStore.ts b/services/app-api/src/parameterStore/emailParameterStore/emailParameterStore.ts index 026bfb095d..26fb63601c 100644 --- a/services/app-api/src/parameterStore/emailParameterStore/emailParameterStore.ts +++ b/services/app-api/src/parameterStore/emailParameterStore/emailParameterStore.ts @@ -8,8 +8,10 @@ import { getCmsReviewHelpEmailLocal, getCmsRateHelpEmail, getCmsRateHelpEmailLocal, - getDMCPEmails, - getDMCPEmailsLocal, + getDMCPReviewEmails, + getDMCPSubmissionEmails, + getDMCPReviewEmailsLocal, + getDMCPSubmissionEmailsLocal, getOACTEmails, getOACTEmailsLocal, getDMCOEmails, @@ -31,7 +33,8 @@ export type EmailParameterStore = { getDevReviewTeamEmails: () => Promise getCmsReviewHelpEmail: () => Promise getCmsRateHelpEmail: () => Promise - getDMCPEmails: () => Promise + getDMCPReviewEmails: () => Promise + getDMCPSubmissionEmails: () => Promise getOACTEmails: () => Promise getDMCOEmails: () => Promise getSourceEmail: () => Promise @@ -45,7 +48,8 @@ function newLocalEmailParameterStore(): EmailParameterStore { getDevReviewTeamEmails: getDevReviewTeamEmailsLocal, getCmsReviewHelpEmail: getCmsReviewHelpEmailLocal, getCmsRateHelpEmail: getCmsRateHelpEmailLocal, - getDMCPEmails: getDMCPEmailsLocal, + getDMCPReviewEmails: getDMCPReviewEmailsLocal, + getDMCPSubmissionEmails: getDMCPSubmissionEmailsLocal, getOACTEmails: getOACTEmailsLocal, getDMCOEmails: getDMCOEmailsLocal, getSourceEmail: getSourceEmailLocal, @@ -61,7 +65,8 @@ function newAWSEmailParameterStore(): EmailParameterStore { getCmsReviewHelpEmail: getCmsReviewHelpEmail, getCmsRateHelpEmail: getCmsRateHelpEmail, getDMCOEmails: getDMCOEmails, - getDMCPEmails: getDMCPEmails, + getDMCPReviewEmails: getDMCPReviewEmails, + getDMCPSubmissionEmails: getDMCPSubmissionEmails, getOACTEmails: getOACTEmails, getSourceEmail: getSourceEmail, getHelpDeskEmail: getHelpDeskEmail, diff --git a/services/app-api/src/parameterStore/emailParameterStore/index.ts b/services/app-api/src/parameterStore/emailParameterStore/index.ts index 32fa0c28fb..9ab40db73f 100644 --- a/services/app-api/src/parameterStore/emailParameterStore/index.ts +++ b/services/app-api/src/parameterStore/emailParameterStore/index.ts @@ -18,7 +18,14 @@ export { } from './cmsRateHelpEmail/getCmsRateHelpEmail' export { getDMCOEmails, getDMCOEmailsLocal } from './dmcoEmails/getDMCOEmails' export { getOACTEmails, getOACTEmailsLocal } from './oactEmails/getOACTEmails' -export { getDMCPEmails, getDMCPEmailsLocal } from './dmcpEmails/getDMCPEmails' +export { + getDMCPReviewEmails, + getDMCPReviewEmailsLocal, +} from './dmcpEmails/getDMCPReviewEmails' +export { + getDMCPSubmissionEmails, + getDMCPSubmissionEmailsLocal, +} from './dmcpEmails/getDMCPSubmissionEmails' export { getSourceEmail, getSourceEmailLocal, diff --git a/services/app-api/src/resolvers/configureResolvers.ts b/services/app-api/src/resolvers/configureResolvers.ts index e216d17564..3ff848678b 100644 --- a/services/app-api/src/resolvers/configureResolvers.ts +++ b/services/app-api/src/resolvers/configureResolvers.ts @@ -74,7 +74,11 @@ export function configureResolvers( ), updateContract: updateContract(store), updateCMSUser: updateCMSUserResolver(store), - createQuestion: createQuestionResolver(store, emailer), + createQuestion: createQuestionResolver( + store, + emailParameterStore, + emailer + ), createQuestionResponse: createQuestionResponseResolver(store), }, User: { diff --git a/services/app-api/src/resolvers/questionResponse/createQuestion.test.ts b/services/app-api/src/resolvers/questionResponse/createQuestion.test.ts index 960df4b087..e2f4d54f4d 100644 --- a/services/app-api/src/resolvers/questionResponse/createQuestion.test.ts +++ b/services/app-api/src/resolvers/questionResponse/createQuestion.test.ts @@ -9,6 +9,7 @@ import { indexTestQuestions, defaultFloridaProgram, } from '../../testHelpers/gqlHelpers' +import { getTestStateAnalystsEmails } from '../../testHelpers/parameterStoreHelpers' import { packageName } from '../../../../app-web/src/common-code/healthPlanFormDataType' import { assertAnError, assertAnErrorCode } from '../../testHelpers' import { @@ -268,7 +269,7 @@ describe('createQuestion', () => { `users without an assigned division are not authorized to create a question` ) }) - it('send state email to state contacts and all submitters when unlocking submission succeeds', async () => { + it('send state email to state contacts and all submitters when submitting a question succeeds', async () => { const config = testEmailConfig() const mockEmailer = testEmailer(config) //mock invoke email submit lambda @@ -321,11 +322,137 @@ describe('createQuestion', () => { `CMS asked questions about ${name}` ), bodyHTML: expect.stringContaining( - `Open the submission in MC-Review to answer questions` + `http://localhost/submissions/${sub.id}/question-and-answers` ), }) ) }) + + it('send CMS email to state analysts if question is successfully submitted', async () => { + const config = testEmailConfig() + const mockEmailer = testEmailer(config) + //mock invoke email submit lambda + const stateServer = await constructTestPostgresServer() + const cmsServer = await constructTestPostgresServer({ + context: { + user: cmsUser, + }, + emailer: mockEmailer, + }) + + const stateSubmission = + await createAndSubmitTestHealthPlanPackage(stateServer) + + await createTestQuestion(cmsServer, stateSubmission.id) + + const currentRevision = stateSubmission.revisions[0].node.formDataProto + + const sub = base64ToDomain(currentRevision) + if (sub instanceof Error) { + throw sub + } + + const programs = [defaultFloridaProgram()] + const name = packageName( + sub.stateCode, + sub.stateNumber, + sub.programIDs, + programs + ) + const stateAnalystsEmails = getTestStateAnalystsEmails(sub.stateCode) + + const cmsEmails = [ + ...config.devReviewTeamEmails, + ...stateAnalystsEmails, + ] + + // email subject line is correct for CMS email + // email is sent to the state anaylsts since it + // was submitted by a DCMO user + // Mock emailer is called 2 times, + // first called to send the state email, then to CMS + expect(mockEmailer.sendEmail).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + subject: expect.stringContaining( + `[LOCAL] Questions sent for ${name}` + ), + sourceEmail: config.emailSource, + toAddresses: expect.arrayContaining(Array.from(cmsEmails)), + bodyText: expect.stringContaining( + `DMCO sent questions to the state for submission ${name}` + ), + bodyHTML: expect.stringContaining( + `http://localhost/submissions/${sub.id}/question-and-answers` + ), + }) + ) + }) + + it('send CMS email to state analysts with correct round number if multiple questions have been asked', async () => { + const config = testEmailConfig() + const mockEmailer = testEmailer(config) + //mock invoke email submit lambda + const stateServer = await constructTestPostgresServer() + const cmsServer = await constructTestPostgresServer({ + context: { + user: cmsUser, + }, + emailer: mockEmailer, + }) + const cmsDMCPUser = testCMSUser({ divisionAssignment: 'DMCP' }) + const cmsDMCPServer = await constructTestPostgresServer({ + context: { + user: cmsDMCPUser, + }, + emailer: mockEmailer, + }) + const stateSubmission = + await createAndSubmitTestHealthPlanPackage(stateServer) + + await createTestQuestion(cmsDMCPServer, stateSubmission.id) + await createTestQuestion(cmsServer, stateSubmission.id) + await createTestQuestion(cmsServer, stateSubmission.id) + + const currentRevision = stateSubmission.revisions[0].node.formDataProto + + const sub = base64ToDomain(currentRevision) + if (sub instanceof Error) { + throw sub + } + + const programs = [defaultFloridaProgram()] + const name = packageName( + sub.stateCode, + sub.stateNumber, + sub.programIDs, + programs + ) + const stateAnalystsEmails = getTestStateAnalystsEmails(sub.stateCode) + + const cmsEmails = [ + ...config.devReviewTeamEmails, + ...stateAnalystsEmails, + ] + + // email subject line is correct for CMS email + // email is sent to the state anaylsts since it + // was submitted by a DCMO user + // Mock emailer is called 4 times, + // first called to send the state email, then to CMS, two times each + expect(mockEmailer.sendEmail).toHaveBeenNthCalledWith( + 6, + expect.objectContaining({ + subject: expect.stringContaining( + `[LOCAL] Questions sent for ${name}` + ), + sourceEmail: config.emailSource, + toAddresses: expect.arrayContaining(Array.from(cmsEmails)), + bodyText: expect.stringContaining('Round: 2'), + }) + ) + }) + it('does not send any emails if submission fails', async () => { const mockEmailer = testEmailer() const cmsServer = await constructTestPostgresServer({ diff --git a/services/app-api/src/resolvers/questionResponse/createQuestion.ts b/services/app-api/src/resolvers/questionResponse/createQuestion.ts index 40d16f81bb..5a705696eb 100644 --- a/services/app-api/src/resolvers/questionResponse/createQuestion.ts +++ b/services/app-api/src/resolvers/questionResponse/createQuestion.ts @@ -12,9 +12,11 @@ import { isStoreError } from '../../postgres' import { GraphQLError } from 'graphql' import { isValidCmsDivison } from '../../domain-models' import type { Emailer } from '../../emailer' +import type { EmailParameterStore } from '../../parameterStore' export function createQuestionResolver( store: Store, + emailParameterStore: EmailParameterStore, emailer: Emailer ): MutationResolvers['createQuestion'] { return async (_parent, { input }, context) => { @@ -93,6 +95,16 @@ export function createQuestionResolver( }) } + const allQuestions = await store.findAllQuestionsByContract( + contractResult.id + ) + if (allQuestions instanceof Error) { + const errMessage = `Issue finding all questions associated with the contract: ${contractResult.id}` + logError('createQuestion', errMessage) + setErrorAttributesOnActiveSpan(errMessage, span) + throw new Error(errMessage) + } + const questionResult = await store.insertQuestion(input, user) if (isStoreError(questionResult)) { @@ -102,14 +114,14 @@ export function createQuestionResolver( throw new Error(errMessage) } - const dateAsked = new Date() + allQuestions.push(questionResult) + const sendQuestionsStateEmailResult = await emailer.sendQuestionsStateEmail( contractResult.revisions[0], - user, submitterEmails, statePrograms, - dateAsked + allQuestions ) if (sendQuestionsStateEmailResult instanceof Error) { @@ -118,7 +130,9 @@ export function createQuestionResolver( sendQuestionsStateEmailResult ) setErrorAttributesOnActiveSpan('state email failed', span) - throw new GraphQLError('Email failed.', { + const errMessage = `Error sending a state email for + questionID: ${questionResult.id} and contractID: ${contractResult.id}` + throw new GraphQLError(errMessage, { extensions: { code: 'INTERNAL_SERVER_ERROR', cause: 'EMAIL_ERROR', @@ -126,6 +140,39 @@ export function createQuestionResolver( }) } + let stateAnalystsEmails = + await emailParameterStore.getStateAnalystsEmails( + contractResult.stateCode + ) + //If error log it and set stateAnalystsEmails to empty string as to not interrupt the emails. + if (stateAnalystsEmails instanceof Error) { + logError('getStateAnalystsEmails', stateAnalystsEmails.message) + setErrorAttributesOnActiveSpan(stateAnalystsEmails.message, span) + stateAnalystsEmails = [] + } + + const sendQuestionsCMSEmailResult = await emailer.sendQuestionsCMSEmail( + contractResult.revisions[0], + stateAnalystsEmails, + statePrograms, + allQuestions + ) + + if (sendQuestionsCMSEmailResult instanceof Error) { + logError( + 'sendQuestionsCMSEmail - CMS email failed', + sendQuestionsCMSEmailResult + ) + setErrorAttributesOnActiveSpan('CMS email failed', span) + const errMessage = `Error sending a CMS email for + questionID: ${questionResult.id} and contractID: ${contractResult.id}` + throw new GraphQLError(errMessage, { + extensions: { + code: 'INTERNAL_SERVER_ERROR', + cause: 'EMAIL_ERROR', + }, + }) + } logSuccess('createQuestion') setSuccessAttributesOnActiveSpan(span) diff --git a/services/app-api/src/testHelpers/emailerHelpers.ts b/services/app-api/src/testHelpers/emailerHelpers.ts index 054f39e088..6adac8f41f 100644 --- a/services/app-api/src/testHelpers/emailerHelpers.ts +++ b/services/app-api/src/testHelpers/emailerHelpers.ts @@ -20,7 +20,8 @@ const testEmailConfig = (): EmailConfiguration => ({ cmsReviewHelpEmailAddress: '"MCOG Example" ', cmsRateHelpEmailAddress: '"Rates Example" ', oactEmails: ['ratesreview@example.com'], - dmcpEmails: ['policyreview1@example.com'], + dmcpReviewEmails: ['policyreview1@example.com'], + dmcpSubmissionEmails: ['policyreviewsubmission1@example.com'], dmcoEmails: ['overallreview@example.com'], helpDeskEmail: '"MC-Review Help Desk" ', }) @@ -37,7 +38,8 @@ const testDuplicateEmailConfig: EmailConfiguration = { cmsReviewHelpEmailAddress: 'duplicate@example.com', cmsRateHelpEmailAddress: 'duplicate@example.com', oactEmails: ['duplicate@example.com', 'duplicate@example.com'], - dmcpEmails: ['duplicate@example.com', 'duplicate@example.com'], + dmcpReviewEmails: ['duplicate@example.com', 'duplicate@example.com'], + dmcpSubmissionEmails: ['duplicate@example.com', 'duplicate@example.com'], dmcoEmails: ['duplicate@example.com', 'duplicate@example.com'], helpDeskEmail: 'duplicate@example.com', } diff --git a/services/app-api/src/testHelpers/gqlHelpers.ts b/services/app-api/src/testHelpers/gqlHelpers.ts index baaedc3648..654f079511 100644 --- a/services/app-api/src/testHelpers/gqlHelpers.ts +++ b/services/app-api/src/testHelpers/gqlHelpers.ts @@ -110,7 +110,8 @@ const constructTestEmailer = (): Emailer => { cmsReviewHelpEmailAddress: 'mcog@example.com', cmsRateHelpEmailAddress: 'rates@example.com', oactEmails: ['testRate@example.com'], - dmcpEmails: ['testPolicy@example.com'], + dmcpReviewEmails: ['testPolicy@example.com'], + dmcpSubmissionEmails: ['testPolicySubmission@example.com'], dmcoEmails: ['testDmco@example.com'], helpDeskEmail: 'MC_Review_HelpDesk@example.com>', } diff --git a/services/app-api/src/testHelpers/parameterStoreHelpers.ts b/services/app-api/src/testHelpers/parameterStoreHelpers.ts index c5fafeac35..4e70bd1017 100644 --- a/services/app-api/src/testHelpers/parameterStoreHelpers.ts +++ b/services/app-api/src/testHelpers/parameterStoreHelpers.ts @@ -26,7 +26,10 @@ function mockEmailParameterStoreError(error?: string): EmailParameterStore { getDMCOEmails: async (): Promise => { return new Error(message) }, - getDMCPEmails: async (): Promise => { + getDMCPReviewEmails: async (): Promise => { + return new Error(message) + }, + getDMCPSubmissionEmails: async (): Promise => { return new Error(message) }, getSourceEmail: async (): Promise => { diff --git a/services/app-graphql/src/queries/fetchEmailSettings.graphql b/services/app-graphql/src/queries/fetchEmailSettings.graphql index f271219d53..0bf9d52ca5 100644 --- a/services/app-graphql/src/queries/fetchEmailSettings.graphql +++ b/services/app-graphql/src/queries/fetchEmailSettings.graphql @@ -6,7 +6,8 @@ query fetchEmailSettings { emailSource devReviewTeamEmails oactEmails - dmcpEmails + dmcpReviewEmails + dmcpSubmissionEmails dmcoEmails cmsReviewHelpEmailAddress cmsRateHelpEmailAddress diff --git a/services/app-graphql/src/schema.graphql b/services/app-graphql/src/schema.graphql index 002daff024..bd120a2c04 100644 --- a/services/app-graphql/src/schema.graphql +++ b/services/app-graphql/src/schema.graphql @@ -695,7 +695,8 @@ type EmailConfiguration { emailSource: String! devReviewTeamEmails: [String!]! oactEmails: [String!]! - dmcpEmails: [String!]! + dmcpReviewEmails: [String!]! + dmcpSubmissionEmails: [String!]! dmcoEmails: [String!]! cmsReviewHelpEmailAddress: String! cmsRateHelpEmailAddress: String! diff --git a/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.test.tsx b/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.test.tsx index 208aa2e6c5..34d8bec582 100644 --- a/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.test.tsx +++ b/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.test.tsx @@ -63,7 +63,7 @@ describe('EmailSettings', () => { // Count the table rows const tableRows = await within(table).findAllByRole('row') - expect(tableRows).toHaveLength(5) + expect(tableRows).toHaveLength(6) // Check the table headers expect( diff --git a/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.tsx b/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.tsx index 15bd148f7e..10b5743864 100644 --- a/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.tsx +++ b/services/app-web/src/pages/Settings/EmailSettingsTables/EmailSettingsTables.tsx @@ -64,8 +64,16 @@ const EmailsGeneralTable = ({ config }: { config: EmailConfiguration }) => { None - {formatEmails(config?.dmcpEmails)} - DMCP division emails + {formatEmails(config?.dmcpReviewEmails)} + DMCP division emails used for reviews + + All submissions; excluding CHIP programs and PR + state + + + + {formatEmails(config?.dmcpSubmissionEmails)} + DMCP division emails used for submissions All submissions; excluding CHIP programs and PR state diff --git a/services/app-web/src/pages/Settings/Settings.test.tsx b/services/app-web/src/pages/Settings/Settings.test.tsx index 9564b77a5a..b666de47f0 100644 --- a/services/app-web/src/pages/Settings/Settings.test.tsx +++ b/services/app-web/src/pages/Settings/Settings.test.tsx @@ -50,16 +50,15 @@ describe('Settings', () => { expect(tableAutomated).toBeInTheDocument() const tableRows = await within(tableAutomated).findAllByRole('row') - expect(tableRows).toHaveLength(5) + expect(tableRows).toHaveLength(6) // Check analysts table const tableAnalysts = await screen.findByRole('table', { name: 'Analyst emails', }) expect(tableAnalysts).toBeInTheDocument() - const tableRowsAnalysts = await within(tableAnalysts).findAllByRole( - 'row' - ) + const tableRowsAnalysts = + await within(tableAnalysts).findAllByRole('row') expect(tableRowsAnalysts).toHaveLength(2) // Check support table diff --git a/services/app-web/src/testHelpers/apolloMocks/emailGQLMock.ts b/services/app-web/src/testHelpers/apolloMocks/emailGQLMock.ts index 7fc953b438..d8b0690140 100644 --- a/services/app-web/src/testHelpers/apolloMocks/emailGQLMock.ts +++ b/services/app-web/src/testHelpers/apolloMocks/emailGQLMock.ts @@ -22,7 +22,10 @@ export const fetchEmailSettings = cmsRateHelpEmailAddress: 'rates@example.com', helpDeskEmail: 'helpdesk@example.com', oactEmails: ['testRate@example.com'], - dmcpEmails: ['testPolicy@example.com'], + dmcpReviewEmails: ['testPolicy@example.com'], + dmcpSubmissionEmails: [ + 'testPolicySubmission@example.com', + ], dmcoEmails: ['testDmco@example.com'], }, stateAnalysts: [