Skip to content

Commit

Permalink
[EASI-2713] Email Notification: Model Plan Dates Changed (#609)
Browse files Browse the repository at this point in the history
* feat: add ams model id and other fields to general characteristics

* chore: migrated model abbreviation to model plan

* chore: add role to plan discussion, begin updating store queries

* feat: updated sql to include role

* chore: updated user_role naming to be consistent, fix: most recent discussion role endpoint

* chore: updated postman collection

* chore: cleaned up old migration

* chore: removed testing code

* chore: removed old ghost code from branch-off

* chore: go mod tidy

* fix: updated inputs to fix broken tests

* fix: added user role to plan discussion test inputs

* chore: updated postman collection

* feat: added user role description to plan discussion with constraints and unit tests

* wip: email notifications when model plan dates change

* fix: reverted file to main version

* chore: removed debug print code

* chore: fix sql constraints, remove trigger

* wip: bug fixes and refactoring

* fix: added userRole and userRoleDiscussion to discussion replies, improved constraints

* fix: server tests relating to discussion reply create input

* chore: simplified email recipients table, added email recipient seeding

* chore: fixed broken unit test

* fix: resolved broken unit test

* chore: simplified some unnecessary code

* Merge branch 'EASI-2713/email_notif_on_model_plan_dates_changed' of github.com:CMSgov/mint-app into EASI-2713/email_notif_on_model_plan_dates_changed
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.

* chore: cleaned up migrations from merging features

* chore: migrated date changed recipient emails to envrc as we don't have a pipeline for loading configs before dbseed runs which leads to unpredictable first-run behavior on the server

* chore: removed ghost code

* chore: added checkDateFieldChanged documentation

* wip: design changes to simplify model plan date changes email

* fix: composing date ranges into common key structs

* chore: several major bugs resolved regarding email date changes

* chore: updated unit tests

* chore: removed debug print and updated gomod

* chore: removed gomod change

* chore: simplified template and removed unnecessary data structure simplifying conversion code

* chore: restructured code and removed debug print statements

* fix: date change evaluation bugs

* chore: removed print statements, added test coverage TODOs

* chore: added test coverage to plan_basics_helper

* Added 'copytime' to non-range fields to prevent pointer updates from causing issues

---------

Co-authored-by: Clay Benson <[email protected]>
  • Loading branch information
OddTomBrooks and ClayBenson94 authored Jul 24, 2023
1 parent cd70a2d commit 08c9e8c
Show file tree
Hide file tree
Showing 18 changed files with 1,060 additions and 51 deletions.
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export EMAIL_TEMPLATE_DIR=$APP_DIR/pkg/email/templates
export [email protected]
export [email protected]
export [email protected]
export [email protected],[email protected]

# AWS variables
export AWS_REGION=us-west-2
Expand Down
60 changes: 36 additions & 24 deletions cmd/dbseed/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,23 @@ func (s *Seeder) SeedData() {

// Seed a plan with some information already in it
planWithBasics := s.createModelPlan("Plan with Basics", "MINT")
s.updatePlanBasics(planWithBasics, map[string]interface{}{
"modelType": models.MTVoluntary,
"goal": "Some goal",
"cmsCenters": []string{"CMMI", "OTHER"},
"cmsOther": "SOME OTHER CMS CENTER",
"cmmiGroups": []string{"PATIENT_CARE_MODELS_GROUP", "SEAMLESS_CARE_MODELS_GROUP"},
"completeICIP": "2020-05-13T20:47:50.12Z",
"phasedIn": true,
"clearanceStarts": time.Now(),
"highLevelNote": "Some high level note",
})
s.updatePlanBasics(
nil,
nil,
email.AddressBook{},
planWithBasics,
map[string]interface{}{
"modelType": models.MTVoluntary,
"goal": "Some goal",
"cmsCenters": []string{"CMMI", "OTHER"},
"cmsOther": "SOME OTHER CMS CENTER",
"cmmiGroups": []string{"PATIENT_CARE_MODELS_GROUP", "SEAMLESS_CARE_MODELS_GROUP"},
"completeICIP": "2020-05-13T20:47:50.12Z",
"phasedIn": true,
"clearanceStarts": time.Now(),
"highLevelNote": "Some high level note",
},
)
s.existingModelLinkCreate(planWithBasics, []int{links[3].ID, links[4].ID}, nil)

// Seed a plan with collaborators
Expand Down Expand Up @@ -189,19 +195,25 @@ func (s *Seeder) SeedData() {
UserName: "BTAL",
TeamRole: models.TeamRoleLeadership,
})
s.updatePlanBasics(sampleModelPlan, map[string]interface{}{
"amsModelID": "123",
"demoCode": "1",
"modelType": models.MTVoluntary,
"goal": "Some goal",
"cmsCenters": []string{"CMMI", "OTHER"},
"cmsOther": "SOME OTHER CMS CENTER",
"cmmiGroups": []string{"PATIENT_CARE_MODELS_GROUP", "SEAMLESS_CARE_MODELS_GROUP"},
"completeICIP": "2020-05-13T20:47:50.12Z",
"phasedIn": true,
"clearanceStarts": time.Now(),
"highLevelNote": "Some high level note",
})
s.updatePlanBasics(
nil,
nil,
email.AddressBook{},
sampleModelPlan,
map[string]interface{}{
"amsModelID": "123",
"demoCode": "1",
"modelType": models.MTVoluntary,
"goal": "Some goal",
"cmsCenters": []string{"CMMI", "OTHER"},
"cmsOther": "SOME OTHER CMS CENTER",
"cmmiGroups": []string{"PATIENT_CARE_MODELS_GROUP", "SEAMLESS_CARE_MODELS_GROUP"},
"completeICIP": "2020-05-13T20:47:50.12Z",
"phasedIn": true,
"clearanceStarts": time.Now(),
"highLevelNote": "Some high level note",
},
)

operationalNeeds := s.getOperationalNeedsByModelPlanID(planWithDocuments.ID)
if len(operationalNeeds) < 1 {
Expand Down
19 changes: 17 additions & 2 deletions cmd/dbseed/resolver_wrappers.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,30 @@ func (s *Seeder) updateModelPlan(mp *models.ModelPlan, changes map[string]interf
// updatePlanBasics is a wrapper for resolvers.PlanBasicsGetByModelPlanID and resolvers.UpdatePlanBasics
// It will panic if an error occurs, rather than bubbling the error up
// It will always update the Plan Basics object with the principal value of the Model Plan's "createdBy"
func (s *Seeder) updatePlanBasics(mp *models.ModelPlan, changes map[string]interface{}) *models.PlanBasics {
func (s *Seeder) updatePlanBasics(
emailService oddmail.EmailService,
emailTemplateService email.TemplateService,
addressBook email.AddressBook,
mp *models.ModelPlan,
changes map[string]interface{},
) *models.PlanBasics {
princ := s.getTestPrincipalByUUID(mp.CreatedBy)

basics, err := resolvers.PlanBasicsGetByModelPlanIDLOADER(s.Config.Context, mp.ID)
if err != nil {
panic(err)
}

updated, err := resolvers.UpdatePlanBasics(s.Config.Logger, basics.ID, changes, princ, s.Config.Store)
updated, err := resolvers.UpdatePlanBasics(
s.Config.Logger,
basics.ID,
changes,
princ,
s.Config.Store,
emailService,
emailTemplateService,
addressBook,
)
if err != nil {
panic(err)
}
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ services:
- EMAIL_HOST=email
- EMAIL_PORT=1025
- MINT_TEAM_EMAIL
- DATE_CHANGED_RECIPIENT_EMAILS
- EMAIL_SENDER
- EMAIL_ENABLED
- [email protected]
Expand Down
4 changes: 4 additions & 0 deletions pkg/appconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ const AccessibilityTeamEmailKey = "ACCESSIBILITY_TEAM_EMAIL"
// MINTTeamEmailKey is the key for the receiving email for the MINT team
const MINTTeamEmailKey = "MINT_TEAM_EMAIL"

// DateChangedRecipientEmailsKey is the key for the receiving email addresses for
// the model plan date changed email notification
const DateChangedRecipientEmailsKey = "DATE_CHANGED_RECIPIENT_EMAILS"

// EmailHostKey is the key for getting the email service's host
const EmailHostKey = "EMAIL_HOST"

Expand Down
4 changes: 4 additions & 0 deletions pkg/email/address_book.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ type AddressBook struct {

// MINTTeamEmail is the email address of the MINT team
MINTTeamEmail string

// ModelPlanDateChangedRecipients is the list of email addresses that should
// receive notifications when one or more model plan dates are changed
ModelPlanDateChangedRecipients []string
}
25 changes: 25 additions & 0 deletions pkg/email/model_plan_date_changed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package email

import "time"

// ModelPlanDateChangedSubjectContent defines the parameters necessary for the corresponding email subject
type ModelPlanDateChangedSubjectContent struct {
ModelName string
}

// DateChange defines the parameters necessary for parsing date changes, both singular and ranges
// If the OldRange and NewRange are both nil, then the change is singular
type DateChange struct {
Field string
IsRange bool
OldDate, NewDate *time.Time
OldRangeStart, OldRangeEnd, NewRangeStart, NewRangeEnd *time.Time
}

// ModelPlanDateChangedBodyContent defines the parameters necessary for the corresponding email body
type ModelPlanDateChangedBodyContent struct {
ClientAddress string
ModelName string
ModelID string
DateChanges []DateChange
}
14 changes: 14 additions & 0 deletions pkg/email/template_service_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ var planDiscussionCreatedSubjectTemplate string
//go:embed templates/plan_discussion_created_body.html
var planDiscussionCreatedBodyTemplate string

// ModelPlanDateChangedTemplateName is the template name definition for the corresponding email template
const ModelPlanDateChangedTemplateName string = "model_plan_date_changed"

//go:embed templates/model_plan_date_changed_subject.html
var modelPlanDateChangedSubjectTemplate string

//go:embed templates/model_plan_date_changed_body.html
var modelPlanDateChangedBodyTemplate string

// TemplateServiceImpl is an implementation-specific structure loading all resources necessary for server execution
type TemplateServiceImpl struct {
templateCache *emailTemplates.TemplateCache
Expand Down Expand Up @@ -99,6 +108,11 @@ func (t *TemplateServiceImpl) Load() error {
return err
}

err = t.loadEmailTemplate(ModelPlanDateChangedTemplateName, modelPlanDateChangedSubjectTemplate, modelPlanDateChangedBodyTemplate)
if err != nil {
return err
}

return nil
}

Expand Down
131 changes: 131 additions & 0 deletions pkg/email/templates/model_plan_date_changed_body.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Content</title>
<style type="text/css">
/* CSS Reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* General */
body {
padding-left: 5px;
text-align: left;
}

/* Headings */
h1 {
font-size: 30pt;
font-weight: bold;
font-style: normal;
color: #1B1B1B;
font-family: 'Public Sans', sans-serif;
text-decoration: none;
}

h2 {
font-size: 24pt;
font-weight: bold;
font-style: normal;
color: #1B1B1B;
font-family: 'Public Sans', sans-serif;
text-decoration: none;
}

h3 {
font-size: 16.5pt;
font-weight: bold;
font-style: normal;
color: #1B1B1B;
font-family: 'Public Sans', sans-serif;
text-decoration: none;
line-height: 1.4;
}

/* Subtitle */
.subtitle {
font-size: 15.5pt;
font-weight: normal;
font-style: normal;
color: #71767A;
font-family: 'Public Sans', sans-serif;
text-decoration: none;
}

/* Paragraph */
p {
font-size: 12pt;
font-weight: normal;
font-style: normal;
color: #1B1B1B;
font-family: 'Public Sans', sans-serif;
text-decoration: none;
line-height: 1.4;
}

/* Links */
a {
font-size: 12pt;
color: #005EA2;
font-family: 'Public Sans', sans-serif;
text-decoration: none;
}
</style>
</head>
<body>
<h1 style="padding-top: 3pt;">MINT</h1>
<p class="subtitle" style="padding-top: 11pt;">The Model Innovation Tool</p>
<br/>
<h2 style="padding-top: 14pt;">Dates updated for {{.ModelName}}</h2>
<br/>
<br/>
<h3>Anticipated high level timeline</h3>
<br />

{{define "oldDate"}}
{{if .}}
{{.Format "01/02/2006"}}
{{else}}
no date entered
{{end}}
{{end}}

{{define "newDate"}}
{{if .}}
{{.Format "01/02/2006"}}
{{else}}
no date entered
{{end}}
{{end}}

{{range .DateChanges}}
<p>
<b>{{.Field}}:</b>
<br />
{{if .IsRange}}
<span style="color: red; text-decoration: line-through;">
{{template "oldDate" .OldRangeStart}} - {{template "oldDate" .OldRangeEnd}}<br/>
</span>
{{template "newDate" .NewRangeStart}} - {{template "newDate" .NewRangeEnd}}
{{else}}
<span style="color: red; text-decoration: line-through;">{{template "oldDate" .OldDate}}</span><br/>
{{template "newDate" .NewDate}}
{{end}}
<br />
<br />
</p>
{{end}}

<br/>
<p style="padding-top: 14pt;">
<a href="{{.ClientAddress}}/models/{{.ModelID}}/task-list/" class="link">
View this Model Plan in MINT
</a>
</p>
</body>
</html>
1 change: 1 addition & 0 deletions pkg/email/templates/model_plan_date_changed_subject.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dates updated for {{.ModelName}}
Loading

0 comments on commit 08c9e8c

Please sign in to comment.