diff --git a/server/build.gradle b/server/build.gradle index 62d6854d5..601c5657c 100755 --- a/server/build.gradle +++ b/server/build.gradle @@ -115,6 +115,8 @@ dependencies { implementation("io.micronaut.reactor:micronaut-reactor") implementation("io.micrometer:context-propagation") + implementation 'ch.digitalfondue.mjml4j:mjml4j:1.0.3' + testRuntimeOnly "org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion" testRuntimeOnly "org.seleniumhq.selenium:selenium-firefox-driver:$seleniumVersion" @@ -213,3 +215,5 @@ java { } run.jvmArgs('-Dcom.sun.management.jmxremote', '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000') + + diff --git a/server/src/main/java/com/objectcomputing/checkins/notifications/email/MailJetFactory.java b/server/src/main/java/com/objectcomputing/checkins/notifications/email/MailJetFactory.java index 55419e4d4..6e755a229 100644 --- a/server/src/main/java/com/objectcomputing/checkins/notifications/email/MailJetFactory.java +++ b/server/src/main/java/com/objectcomputing/checkins/notifications/email/MailJetFactory.java @@ -14,6 +14,7 @@ public class MailJetFactory { public static final String HTML_FORMAT = "html"; + public static final String MJML_FORMAT = "mjml"; public static final String TEXT_FORMAT = "text"; @Bean @@ -34,6 +35,13 @@ EmailSender getHtmlSender(MailJetSender sender) { return sender; } + @Singleton + @Named(MJML_FORMAT) + EmailSender getMjmlSender(MailJetSender sender) { + sender.setEmailFormat(MailJetSender.MJMLPART); + return sender; + } + @Singleton @Named(TEXT_FORMAT) EmailSender getTextSender(MailJetSender sender) { diff --git a/server/src/main/java/com/objectcomputing/checkins/notifications/email/MailJetSender.java b/server/src/main/java/com/objectcomputing/checkins/notifications/email/MailJetSender.java index f6b7e98f5..05bbdc4ce 100644 --- a/server/src/main/java/com/objectcomputing/checkins/notifications/email/MailJetSender.java +++ b/server/src/main/java/com/objectcomputing/checkins/notifications/email/MailJetSender.java @@ -5,6 +5,7 @@ import com.mailjet.client.MailjetResponse; import com.mailjet.client.errors.MailjetException; import com.mailjet.client.resource.Emailv31; +import ch.digitalfondue.mjml4j.Mjml4j; import com.objectcomputing.checkins.exceptions.BadArgException; import io.micronaut.context.annotation.Prototype; import io.micronaut.context.annotation.Requires; @@ -20,6 +21,7 @@ @Prototype @Requires(bean = MailJetConfiguration.class) public class MailJetSender implements EmailSender { + public static final String MJMLPART = "MJMLPART"; private static final Logger LOG = LoggerFactory.getLogger(MailJetSender.class); private final MailjetClient client; @@ -94,6 +96,16 @@ public void sendEmail(String fromName, String fromAddress, String subject, Strin .put("Email", fromAddress) .put("Name", fromName); + String modifiedEmailFormat = emailFormat; + if (modifiedEmailFormat.equals(MJMLPART)) { + // Convert the MJML to HTML and update the local email format. + var configuration = new Mjml4j.Configuration("en"); + content = Mjml4j.render(content, configuration); + modifiedEmailFormat = Emailv31.Message.HTMLPART; + } + final String localEmailFormat = modifiedEmailFormat; + final String localContent = content; + emailBatches.forEach((recipientList) -> { MailjetRequest request = new MailjetRequest(Emailv31.resource) .property(Emailv31.MESSAGES, new JSONArray() @@ -102,7 +114,7 @@ public void sendEmail(String fromName, String fromAddress, String subject, Strin .put(Emailv31.Message.TO, new JSONArray().put(sender)) .put(Emailv31.Message.BCC, recipientList) .put(Emailv31.Message.SUBJECT, subject) - .put(emailFormat, content))); + .put(localEmailFormat, localContent))); try { MailjetResponse response = client.post(request); LOG.info("Mailjet response status: {}", response.getStatus()); @@ -134,10 +146,12 @@ public boolean sendEmailReceivesStatus(String fromName, String fromAddress, Stri @Override public void setEmailFormat(String format) { - if (format.equals(Emailv31.Message.HTMLPART) || format.equals(Emailv31.Message.TEXTPART)) { + if (format.equals(MJMLPART) || + format.equals(Emailv31.Message.HTMLPART) || + format.equals(Emailv31.Message.TEXTPART)) { this.emailFormat = format; } else { - throw new BadArgException(String.format("Email format must be either HTMLPART or TEXTPART, got %s", format)); + throw new BadArgException(String.format("Email format must be either HTMLPART, MJMLPART or TEXTPART, got %s", format)); } } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java index 908ac8093..180d8fef3 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestServicesImpl.java @@ -6,7 +6,9 @@ import com.objectcomputing.checkins.exceptions.PermissionException; import com.objectcomputing.checkins.notifications.email.EmailSender; import com.objectcomputing.checkins.notifications.email.MailJetFactory; +import com.objectcomputing.checkins.services.email.Email; import com.objectcomputing.checkins.services.memberprofile.MemberProfile; +import com.objectcomputing.checkins.services.memberprofile.MemberProfileUtils; import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices; import com.objectcomputing.checkins.services.memberprofile.currentuser.CurrentUserServices; import com.objectcomputing.checkins.services.reviews.ReviewAssignment; @@ -14,11 +16,16 @@ import com.objectcomputing.checkins.services.reviews.ReviewPeriod; import com.objectcomputing.checkins.services.reviews.ReviewPeriodRepository; import com.objectcomputing.checkins.util.Util; +import io.micronaut.context.annotation.Value; +import io.micronaut.core.io.Readable; +import io.micronaut.core.io.IOUtils; import jakarta.inject.Named; import jakarta.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedReader; +import java.time.format.DateTimeFormatter; import java.time.LocalDate; import java.util.ArrayList; import java.util.HashSet; @@ -44,11 +51,23 @@ public class FeedbackRequestServicesImpl implements FeedbackRequestServices { private final String notificationSubject; private final String webURL; + private enum CompletionEmailType { REVIEWERS, SUPERVISOR } + private record ReviewPeriodInfo(String subject, LocalDate closeDate) {} + @Value("classpath:mjml/feedback_request.mjml") + private Readable feedbackRequestTemplate; + @Value("classpath:mjml/update_request.mjml") + private Readable updateRequestTemplate; + @Value("classpath:mjml/reviewer_email.mjml") + private Readable reviewerTemplate; + @Value("classpath:mjml/supervisor_email.mjml") + private Readable supervisorTemplate; + public FeedbackRequestServicesImpl(FeedbackRequestRepository feedbackReqRepository, CurrentUserServices currentUserServices, MemberProfileServices memberProfileServices, - ReviewPeriodRepository reviewPeriodRepository, ReviewAssignmentRepository reviewAssignmentRepository, - @Named(MailJetFactory.HTML_FORMAT) EmailSender emailSender, + ReviewPeriodRepository reviewPeriodRepository, + ReviewAssignmentRepository reviewAssignmentRepository, + @Named(MailJetFactory.MJML_FORMAT) EmailSender emailSender, CheckInsConfiguration checkInsConfiguration ) { this.feedbackReqRepository = feedbackReqRepository; @@ -107,19 +126,29 @@ public FeedbackRequest save(FeedbackRequest feedbackRequest) { public void sendNewRequestEmail(FeedbackRequest storedRequest) { MemberProfile creator = memberProfileServices.getById(storedRequest.getCreatorId()); + MemberProfile reviewer = memberProfileServices.getById(storedRequest.getRecipientId()); MemberProfile requestee = memberProfileServices.getById(storedRequest.getRequesteeId()); - String senderName = creator.getFirstName() + " " + creator.getLastName(); - String newContent = "

You have received a feedback request.

" + - "

" + senderName + " is requesting feedback on " + requestee.getFirstName() + " " + requestee.getLastName() + " from you.

"; - if (storedRequest.getDueDate() != null) { - newContent += "

This request is due on " + storedRequest.getDueDate().getMonth() + " " + storedRequest.getDueDate().getDayOfMonth() + ", " + storedRequest.getDueDate().getYear() + "."; - } - newContent += "

Please go to your unique link at " + webURL + "/feedback/submit?request=" + storedRequest.getId() + " to complete this request.

"; - -// LOG.warn("Pretending to send an email about the new request to "+memberProfileServices.getById(storedRequest.getRecipientId()).getFirstName()); - if (!storedRequest.getRecipientId().equals(storedRequest.getRequesteeId())) { - emailSender.sendEmail(senderName, creator.getWorkEmail(), notificationSubject, newContent, memberProfileServices.getById(storedRequest.getRecipientId()).getWorkEmail()); - } + String senderName = MemberProfileUtils.getFullName(creator); + + String newContent = String.format( + templateToString(feedbackRequestTemplate), + reviewer.getFirstName(), senderName, + storedRequest.getRecipientId().equals(storedRequest.getRequesteeId()) ? + "" : + String.format("on %s ", + MemberProfileUtils.getFullName(requestee)), + storedRequest.getDueDate() == null ? + "This request does not have a due date." : + String.format("This request is due on %s %d, %d.", + storedRequest.getDueDate().getMonth(), + storedRequest.getDueDate().getDayOfMonth(), + storedRequest.getDueDate().getYear()), + String.format("%s/feedback/submit?request=%s", + webURL, storedRequest.getId().toString())); + + emailSender.sendEmail(senderName, creator.getWorkEmail(), + notificationSubject, newContent, + reviewer.getWorkEmail()); } @Override @@ -187,20 +216,20 @@ public FeedbackRequest update(FeedbackRequestUpdateDTO feedbackRequestUpdateDTO) } FeedbackRequest storedRequest = feedbackReqRepository.update(feedbackRequest); + MemberProfile reviewer = memberProfileServices.getById(storedRequest.getRecipientId()); MemberProfile requestee = memberProfileServices.getById(storedRequest.getRequesteeId()); // Send email if the feedback request has been reopened for edits if (originalFeedback.getStatus().equals("submitted") && feedbackRequest.getStatus().equals("sent")) { MemberProfile creator = memberProfileServices.getById(storedRequest.getCreatorId()); + String senderName = MemberProfileUtils.getFullName(creator); + String newContent = String.format( + templateToString(updateRequestTemplate), + reviewer.getFirstName(), senderName, + MemberProfileUtils.getFullName(requestee), + String.format("%s/feedback/submit?request=%s", + webURL, storedRequest.getId().toString())); - String senderName = creator.getFirstName() + " " + creator.getLastName(); - String newContent = "

You have received edit access to a feedback request.

" + - "

" + senderName + - " has reopened the feedback request on " + - requestee.getFirstName() + " " + requestee.getLastName() + " from you." + - "You may make changes to your answers, but you will need to submit the form again when finished.

"; - newContent += "

Please go to your unique link at " + webURL + "/feedback/submit?request=" + storedRequest.getId() + " to complete this request.

"; -// LOG.warn("Pretending to send an email about the reopened request to "+memberProfileServices.getById(storedRequest.getRecipientId()).getFirstName()); - emailSender.sendEmail(senderName, creator.getWorkEmail(), notificationSubject, newContent, memberProfileServices.getById(storedRequest.getRecipientId()).getWorkEmail()); + emailSender.sendEmail(senderName, creator.getWorkEmail(), notificationSubject, newContent, reviewer.getWorkEmail()); } // Send email if the feedback request has been reassigned @@ -210,12 +239,14 @@ public FeedbackRequest update(FeedbackRequestUpdateDTO feedbackRequestUpdateDTO) // Send self-review completion email to supervisor and pdl if appropriate if (currentUserServices.getCurrentUser().getId().equals(requestee.getId())) { - sendSelfReviewCompletionEmailToPdlAndSupervisor(feedbackRequest); + sendSelfReviewCompletionEmailToSupervisor(feedbackRequest); } - // Send email to reviewers - if (reviewAssignmentsSet != null && reviewAssignmentsSet.size() > 0) { - this.sendSelfReviewCompletionEmailToReviewers(feedbackRequest, reviewAssignmentsSet); + // Send email to reviewers. But, only when the requestee is the + // recipient (i.e., a self-review). + if (reviewAssignmentsSet != null && reviewAssignmentsSet.size() > 0 && + feedbackRequest.getRequesteeId().equals(feedbackRequest.getRecipientId())) { + sendSelfReviewCompletionEmailToReviewers(feedbackRequest, reviewAssignmentsSet); } return storedRequest; @@ -345,120 +376,128 @@ private FeedbackRequest getFromDTO(FeedbackRequestUpdateDTO dto) { } public void sendSelfReviewCompletionEmailToReviewers(FeedbackRequest feedbackRequest, Set reviewAssignmentSet) { - MemberProfile currentUserProfile = currentUserServices.getCurrentUser(); - MemberProfile pdlProfile = null; - MemberProfile supervisorProfile = null; - - try { - if (currentUserProfile.getPdlId() != null) { - pdlProfile = memberProfileServices.getById(currentUserProfile.getPdlId()); - } - } catch (NullPointerException e) { - LOG.error("PDL could not be found for self-review completion email"); - } - - try { - if (currentUserProfile.getSupervisorid() != null) { - supervisorProfile = memberProfileServices.getById(currentUserProfile.getSupervisorid()); - } - } catch (NullPointerException e) { - LOG.error("Supervisor could not be found for self-review completion email"); - } - - String reviewPeriodString = ""; - if (feedbackRequest.getReviewPeriodId() != null) { - Optional reviewPeriodOpt = reviewPeriodRepository.findById(feedbackRequest.getReviewPeriodId()); - if (reviewPeriodOpt.isPresent()) { - ReviewPeriod reviewPeriod = reviewPeriodOpt.get(); - reviewPeriodString = String.format(" for %s", reviewPeriod.getName()); - } - } - - String subject = String.format("%s %s has finished their self-review%s.", currentUserProfile.getFirstName(), currentUserProfile.getLastName(), reviewPeriodString); - StringBuilder bodyBuilder = new StringBuilder(String.format("Self-review has been completed by %s %s%s.
", currentUserProfile.getFirstName(), currentUserProfile.getLastName(), reviewPeriodString)); - - if (pdlProfile != null) { - bodyBuilder.append(String.format("PDL: %s %s
", pdlProfile.getFirstName(), pdlProfile.getLastName())); - } - - if (supervisorProfile != null) { - bodyBuilder.append(String.format("Supervisor: %s %s
", supervisorProfile.getFirstName(), supervisorProfile.getLastName())); - } - - Set recipients = new HashSet<>(); + // Send an email to each reviewer. reviewAssignmentSet.forEach(reviewAssignment -> { MemberProfile memberProfileReviewer = memberProfileServices.getById(reviewAssignment.getReviewerId()); - if (memberProfileReviewer != null && memberProfileReviewer.getWorkEmail() != null) { - recipients.add(memberProfileReviewer.getWorkEmail()); + if (memberProfileReviewer != null && + memberProfileReviewer.getWorkEmail() != null) { + sendSelfReviewCompletionEmail(feedbackRequest, + memberProfileReviewer, + CompletionEmailType.REVIEWERS); } }); - - bodyBuilder.append("
It is now your turn in their review process. Please complete your portion in a timely manner."); - - String body = bodyBuilder.toString(); - - if (recipients.size() > 0) { - try { - emailSender.sendEmail(null, null, subject, body, recipients.toArray(new String[0])); - } catch (Exception e) { - LOG.error("Unable to send self-review completion email to reviewers", e); - } - } } - public void sendSelfReviewCompletionEmailToPdlAndSupervisor(FeedbackRequest feedbackRequest) { + public void sendSelfReviewCompletionEmailToSupervisor(FeedbackRequest feedbackRequest) { MemberProfile currentUserProfile = currentUserServices.getCurrentUser(); - MemberProfile pdlProfile = null; - MemberProfile supervisorProfile = null; - try { - if (currentUserProfile.getPdlId() != null) { - pdlProfile = memberProfileServices.getById(currentUserProfile.getPdlId()); + if (currentUserProfile.getSupervisorid() != null) { + MemberProfile supervisorProfile = + memberProfileServices.getById( + currentUserProfile.getSupervisorid()); + sendSelfReviewCompletionEmail(feedbackRequest, + supervisorProfile, + CompletionEmailType.SUPERVISOR); } - } catch (NullPointerException e) { - LOG.error("PDL could not be found for self-review completion email"); + } catch (NotFoundException e) { + LOG.error("Supervisor could not be found for completion email"); } + } + private void sendSelfReviewCompletionEmail(FeedbackRequest feedbackRequest, + MemberProfile reviewer, + CompletionEmailType emailType) { + // Build the email contents. + Email email; + MemberProfile currentUserProfile = currentUserServices.getCurrentUser(); + switch(emailType) { + case CompletionEmailType.REVIEWERS: + email = buildReviewerEmail(feedbackRequest, reviewer, + currentUserProfile); + break; + default: + case CompletionEmailType.SUPERVISOR: + email = buildSupervisorEmail(feedbackRequest, reviewer, + currentUserProfile); + break; + } + + // Send the email. try { - if (currentUserProfile.getSupervisorid() != null) { - supervisorProfile = memberProfileServices.getById(currentUserProfile.getSupervisorid()); - } - } catch (NullPointerException e) { - LOG.error("Supervisor could not be found for self-review completion email"); + emailSender.sendEmail(null, null, email.getSubject(), + email.getContents(), + reviewer.getWorkEmail()); + } catch (Exception e) { + LOG.error("Unable to send the self-review completion email.", e); } + } + private ReviewPeriodInfo getSelfReviewInfo( + FeedbackRequest feedbackRequest, String name) { String reviewPeriodString = ""; + LocalDate closeDate = null; if (feedbackRequest.getReviewPeriodId() != null) { Optional reviewPeriodOpt = reviewPeriodRepository.findById(feedbackRequest.getReviewPeriodId()); if (reviewPeriodOpt.isPresent()) { ReviewPeriod reviewPeriod = reviewPeriodOpt.get(); + if (reviewPeriod.getCloseDate() != null) { + closeDate = reviewPeriod.getCloseDate().toLocalDate(); + } reviewPeriodString = String.format(" for %s", reviewPeriod.getName()); } } + return new ReviewPeriodInfo( + String.format("%s has finished their self-review%s.", + name, reviewPeriodString), closeDate); + } - String subject = String.format("%s %s has finished their self-review%s.", currentUserProfile.getFirstName(), currentUserProfile.getLastName(), reviewPeriodString); - StringBuilder bodyBuilder = new StringBuilder(String.format("Self-review has been completed by %s %s%s.
", currentUserProfile.getFirstName(), currentUserProfile.getLastName(), reviewPeriodString)); - - Set recipients = new HashSet<>(); - if (pdlProfile != null) { - bodyBuilder.append(String.format("PDL: %s %s
", pdlProfile.getFirstName(), pdlProfile.getLastName())); - recipients.add(pdlProfile.getWorkEmail()); - } - - if (supervisorProfile != null) { - bodyBuilder.append(String.format("Supervisor: %s %s
", supervisorProfile.getFirstName(), supervisorProfile.getLastName())); - recipients.add(supervisorProfile.getWorkEmail()); - } + private Email buildReviewerEmail(FeedbackRequest feedbackRequest, + MemberProfile reviewerProfile, + MemberProfile currentUserProfile) { + String reviewerName = reviewerProfile.getFirstName(); + String revieweeName = MemberProfileUtils.getFullName(currentUserProfile); + UUID requestId = feedbackRequest.getId(); + String selfReviewURL = String.format("%s/feedback/view/responses/?request=%s", webURL, requestId == null ? "none" : requestId.toString()); + ReviewPeriodInfo info = getSelfReviewInfo(feedbackRequest, revieweeName); + LocalDate closeDate = info.closeDate(); + String ending = closeDate == null ? "the review period closes" : + closeDate.format(DateTimeFormatter.ofPattern("MM/dd/yyyy")); + + String body = String.format(templateToString(reviewerTemplate), + revieweeName, reviewerName, revieweeName, + selfReviewURL, ending); + Email email = new Email(); + email.setSubject(info.subject()); + email.setContents(body); + return email; + } - String body = bodyBuilder.toString(); + private Email buildSupervisorEmail(FeedbackRequest feedbackRequest, + MemberProfile supervisorProfile, + MemberProfile currentUserProfile) { + String supervisorName = supervisorProfile == null ? "Supervisor" : + supervisorProfile.getFirstName(); + String revieweeName = MemberProfileUtils.getFullName(currentUserProfile); + UUID requestId = feedbackRequest.getId(); + String selfReviewURL = String.format("%s/feedback/view/responses/?request=%s", webURL, requestId == null ? "none" : requestId.toString()); + ReviewPeriodInfo info = getSelfReviewInfo(feedbackRequest, revieweeName); + + String body = String.format(templateToString(supervisorTemplate), + supervisorName, revieweeName, + selfReviewURL); + + Email email = new Email(); + email.setSubject(info.subject()); + email.setContents(body); + return email; + } - if (pdlProfile != null || supervisorProfile != null) { - try { - emailSender.sendEmail(null, null, subject, body, recipients.toArray(new String[0])); - } catch (Exception e) { - LOG.error("Unable to send self-review completion email to PDL/Supervisor", e); - } + private String templateToString(Readable template) { + try { + return IOUtils.readText(new BufferedReader(template.asReader())); + } catch (Exception ex) { + LOG.error(ex.toString()); + return ""; } } - } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/pulse/PulseEmail.java b/server/src/main/java/com/objectcomputing/checkins/services/pulse/PulseEmail.java index bac3a3f40..2b22eb0e0 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/pulse/PulseEmail.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/pulse/PulseEmail.java @@ -5,18 +5,32 @@ import com.objectcomputing.checkins.notifications.email.MailJetFactory; import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices; +import jakarta.inject.Singleton; import jakarta.inject.Named; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.micronaut.context.annotation.Value; +import io.micronaut.core.io.Readable; +import io.micronaut.core.io.IOUtils; + +import java.io.BufferedReader; import java.util.List; +@Singleton class PulseEmail { + private static final Logger LOG = LoggerFactory.getLogger(PulseEmail.class); private final EmailSender emailSender; private final CheckInsConfiguration checkInsConfiguration; private final MemberProfileServices memberProfileServices; private final String SUBJECT = "Check Out the Pulse Survey!"; + + @Value("classpath:mjml/pulse_email.mjml") + private Readable pulseEmailTemplate; - public PulseEmail(@Named(MailJetFactory.HTML_FORMAT) EmailSender emailSender, + public PulseEmail(@Named(MailJetFactory.MJML_FORMAT) EmailSender emailSender, CheckInsConfiguration checkInsConfiguration, MemberProfileServices memberProfileServices) { this.emailSender = emailSender; @@ -25,43 +39,22 @@ public PulseEmail(@Named(MailJetFactory.HTML_FORMAT) EmailSender emailSender, } private List getActiveTeamMembers() { - List profiles = memberProfileServices.findAll().stream() + List addresses = memberProfileServices.findAll().stream() .filter(p -> p.getTerminationDate() == null) .map(p -> p.getWorkEmail()) .toList(); - return profiles; + return addresses; } private String getEmailContent() { -/* - - - - - - Please fill out your Pulse survey, if you haven't already done so. We want to know how you're doing! - Click here to begin. - - - - -*/ - return String.format(""" - - -
-

-

-
- Please fill out your Pulse survey, if you haven't already done so. We want to know how you're doing!
-

-
- Click here to begin.
-
- - - -""", checkInsConfiguration.getWebAddress()); + try { + return String.format(IOUtils.readText( + new BufferedReader(pulseEmailTemplate.asReader())), + checkInsConfiguration.getWebAddress()); + } catch(Exception ex) { + LOG.error(ex.toString()); + return ""; + } } public void send() { diff --git a/server/src/main/java/com/objectcomputing/checkins/services/pulse/PulseServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/pulse/PulseServicesImpl.java index 3731950de..9743134ad 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/pulse/PulseServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/pulse/PulseServicesImpl.java @@ -11,6 +11,7 @@ import lombok.Getter; import lombok.AllArgsConstructor; +import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.inject.Singleton; @@ -40,6 +41,9 @@ private class Frequency { private final SettingsServices settingsServices; private final Map sent = new HashMap(); + @Inject + private PulseEmail email; + private final DayOfWeek emailDay = DayOfWeek.MONDAY; private String setting = "bi-weekly"; @@ -50,7 +54,7 @@ private class Frequency { ); public PulseServicesImpl( - @Named(MailJetFactory.HTML_FORMAT) EmailSender emailSender, + @Named(MailJetFactory.MJML_FORMAT) EmailSender emailSender, CheckInsConfiguration checkInsConfiguration, MemberProfileServices memberProfileServices, SettingsServices settingsServices) { @@ -93,7 +97,7 @@ public void sendPendingEmail(LocalDate check) { LOG.info("The Pulse Email has already been sent today"); } else { LOG.info("Sending Pulse Email"); - send(); + email.send(); sent.put(key, true); } break; @@ -110,10 +114,4 @@ public void sendPendingEmail(LocalDate check) { } while(start.isBefore(check) || start.isEqual(check)); } } - - private void send() { - PulseEmail email = new PulseEmail(emailSender, checkInsConfiguration, - memberProfileServices); - email.send(); - } } diff --git a/server/src/main/resources/mjml/README.md b/server/src/main/resources/mjml/README.md new file mode 100644 index 000000000..f4977fb7c --- /dev/null +++ b/server/src/main/resources/mjml/README.md @@ -0,0 +1,10 @@ +# Templates + +Place MJML templates here. If they are going to be used within a call to `String.format()`, be sure to double up on any percent signs (i.e., `width="100%%"`). + +### Micronaut Usage + +```java +@Value("classpath:mjml/feedback_request.mjml") +private Readable feedbackRequestTemplate; +``` diff --git a/server/src/main/resources/mjml/feedback_request.mjml b/server/src/main/resources/mjml/feedback_request.mjml new file mode 100644 index 000000000..596b8ade9 --- /dev/null +++ b/server/src/main/resources/mjml/feedback_request.mjml @@ -0,0 +1,36 @@ + + + Feedback Request + Feedback Request + + + + + + + + + + + + Give Your Feedback! + + + + + +

You have received a feedback request.

+
+ Hello, %s! + %s is requesting feedback %sfrom you. + %s + Please go to your unique link to complete this request. +
+
+ + + Thank you for everything you do! + + +
+
diff --git a/server/src/main/resources/mjml/pulse_email.mjml b/server/src/main/resources/mjml/pulse_email.mjml new file mode 100644 index 000000000..769cb9a0e --- /dev/null +++ b/server/src/main/resources/mjml/pulse_email.mjml @@ -0,0 +1,11 @@ + + + + + + Please fill out your Pulse survey, if you haven't already done so. We want to know how you're doing! + Click here to begin. + + + + diff --git a/server/src/main/resources/mjml/reviewer_email.mjml b/server/src/main/resources/mjml/reviewer_email.mjml new file mode 100644 index 000000000..c50aa4b42 --- /dev/null +++ b/server/src/main/resources/mjml/reviewer_email.mjml @@ -0,0 +1,35 @@ + + + Self-Review Completion + Self-Reviews Completion for Reviewer + + + + + + + + + + + + It's Your Turn! + + + + + +

It's time to begin your review of %s

+
+ Hello, %s! + %s has completed their self-review and it can be viewed here. It's your turn to share your thoughts and complete your review. Please complete your review before %s. + +
+
+ + + Thank you for everything you do! + + +
+
diff --git a/server/src/main/resources/mjml/supervisor_email.mjml b/server/src/main/resources/mjml/supervisor_email.mjml new file mode 100644 index 000000000..b9ad37b00 --- /dev/null +++ b/server/src/main/resources/mjml/supervisor_email.mjml @@ -0,0 +1,35 @@ + + + Self-Review Completion + Self-Reviews Completion for Supervisor + + + + + + + + + + + + Self-Review Completion + + + + + +

Your team member has completed their self-review!

+
+ Hello, %s! + %s has completed their self-review. You can view it here. + +
+
+ + + Thank you for everything you do! + + +
+
diff --git a/server/src/main/resources/mjml/update_request.mjml b/server/src/main/resources/mjml/update_request.mjml new file mode 100644 index 000000000..db7588e61 --- /dev/null +++ b/server/src/main/resources/mjml/update_request.mjml @@ -0,0 +1,36 @@ + + + Feedback Request Reopened + Feedback Request Reopened + + + + + + + + + + + + Edit Your Feedback! + + + + + +

You have received edit access to a feedback request.

+
+ Hello, %s! + %s has reopened the feedback request on %s from you. + You may make changes to your answers, but you will need to submit the form again when finished. + Please go to your unique link to complete this request. +
+
+ + + Thank you for everything you do! + + +
+
diff --git a/server/src/test/java/com/objectcomputing/checkins/services/MailJetFactoryReplacement.java b/server/src/test/java/com/objectcomputing/checkins/services/MailJetFactoryReplacement.java index eb4781035..3c96c47ed 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/MailJetFactoryReplacement.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/MailJetFactoryReplacement.java @@ -28,6 +28,13 @@ @Requires(property = "replace.mailjet.factory", value = StringUtils.TRUE) public class MailJetFactoryReplacement { + @Singleton + @Named(MailJetFactory.MJML_FORMAT) + @Replaces(value = EmailSender.class, named = MailJetFactory.MJML_FORMAT) + MockEmailSender getMjmlSender() { + return new MockEmailSender(); + } + @Singleton @Named(MailJetFactory.HTML_FORMAT) @Replaces(value = EmailSender.class, named = MailJetFactory.HTML_FORMAT) diff --git a/server/src/test/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestControllerTest.java b/server/src/test/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestControllerTest.java index 92b7afc82..53e08739d 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestControllerTest.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestControllerTest.java @@ -54,7 +54,7 @@ class FeedbackRequestControllerTest extends TestContainersSuite implements Membe CheckInsConfiguration checkInsConfiguration; @Inject - @Named(MailJetFactory.HTML_FORMAT) + @Named(MailJetFactory.MJML_FORMAT) private MailJetFactoryReplacement.MockEmailSender emailSender; @BeforeEach @@ -63,26 +63,6 @@ void resetMocks() { emailSender.reset(); } - private String createEmailContent(FeedbackRequest storedRequest, UUID requestId, MemberProfile creator, MemberProfile requestee) { - String newContent = "

You have received a feedback request.

" + - "

" + creator.getFirstName() + " " + creator.getLastName() + " is requesting feedback on " + requestee.getFirstName() + " " + requestee.getLastName() + " from you.

"; - if (storedRequest.getDueDate() != null) { - newContent += "

This request is due on " + storedRequest.getDueDate().getMonth() + " " + storedRequest.getDueDate().getDayOfMonth() + ", " + storedRequest.getDueDate().getYear() + "."; - } - newContent += "

Please go to your unique link at " + checkInsConfiguration.getWebAddress() + "/feedback/submit?request=" + requestId + " to complete this request.

"; - return newContent; - } - - private String updateEmailContent(UUID requestId, MemberProfile creator, MemberProfile requestee) { - String newContent = "

You have received edit access to a feedback request.

" + - "

" + creator.getFirstName() + " " + creator.getLastName() + - " has reopened the feedback request on " + - requestee.getFirstName() + " " + requestee.getLastName() + " from you." + - "You may make changes to your answers, but you will need to submit the form again when finished.

"; - newContent += "

Please go to your unique link at " + checkInsConfiguration.getWebAddress() + "/feedback/submit?request=" + requestId + " to complete this request.

"; - return newContent; - } - /** * Converts a {@link FeedbackRequest} to a {@link FeedbackRequestCreateDTO} * @@ -206,16 +186,14 @@ void testCreateFeedbackRequestSendsEmailNow() { String fromName = pdlMemberProfile.getFirstName() + " " + pdlMemberProfile.getLastName(); //verify appropriate email was sent assertTrue(response.getBody().isPresent()); - String correctContent = createEmailContent(feedbackRequest, response.getBody().get().getId(), pdlMemberProfile, employeeMemberProfile); - assertEquals(List.of( - "SEND_EMAIL", - fromName, - pdlMemberProfile.getWorkEmail(), - checkInsConfiguration.getApplication().getFeedback().getRequestSubject(), - correctContent, - recipient.getWorkEmail() - ), - emailSender.events.getFirst() + assertEquals(1, emailSender.events.size()); + validateEmail("SEND_EMAIL", + fromName, + pdlMemberProfile.getWorkEmail(), + checkInsConfiguration.getApplication().getFeedback().getRequestSubject(), + "You have received a feedback request", + recipient.getWorkEmail(), + emailSender.events.getFirst() ); } @@ -1070,16 +1048,14 @@ void testFeedbackRequestEnableEditsSendsEmail() { String fromName = pdl.getFirstName() + " " + pdl.getLastName(); // Verify appropriate email was sent assertTrue(response.getBody().isPresent()); - String correctContent = updateEmailContent(response.getBody().get().getId(), pdl, requestee); - assertEquals(List.of( - "SEND_EMAIL", - fromName, - pdl.getWorkEmail(), - checkInsConfiguration.getApplication().getFeedback().getRequestSubject(), - correctContent, - recipient.getWorkEmail() - ), - emailSender.events.getFirst() + assertEquals(1, emailSender.events.size()); + validateEmail("SEND_EMAIL", + fromName, + pdl.getWorkEmail(), + checkInsConfiguration.getApplication().getFeedback().getRequestSubject(), + "You have received edit access to a feedback request", + recipient.getWorkEmail(), + emailSender.events.getFirst() ); } @@ -1438,4 +1414,18 @@ void testGetMultipleRequesteesBySupervisor() { assertResponseEqualsEntity(feedbackReq, response.getBody().get().get(0)); assertResponseEqualsEntity(feedbackReqTwo, response.getBody().get().get(1)); } + + private void validateEmail(String action, String fromName, + String fromAddress, String subject, + String partial, String toAddress, + List event) { + assertEquals(action, event.get(0)); + assertEquals(fromName, event.get(1)); + assertEquals(fromAddress, event.get(2)); + assertEquals(subject, event.get(3)); + if (partial != null && !partial.isEmpty()) { + assertTrue(event.get(4).contains(partial)); + } + assertEquals(toAddress, event.get(5)); + } } diff --git a/server/src/test/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestTest.java b/server/src/test/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestTest.java index f67bbfa41..e6f095e6c 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestTest.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestTest.java @@ -15,12 +15,16 @@ import com.objectcomputing.checkins.services.reviews.ReviewPeriodRepository; import io.micronaut.context.annotation.Property; import io.micronaut.core.util.StringUtils; +import io.micronaut.runtime.server.EmbeddedServer; import jakarta.inject.Inject; import jakarta.inject.Named; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.mockito.Mock; import org.mockito.Mockito; import java.time.LocalDate; @@ -30,12 +34,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; - import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; @Property(name = "replace.mailjet.factory", value = StringUtils.TRUE) // Disabled in nativeTest, as we get an exception from Mockito @@ -43,36 +48,56 @@ @DisabledInNativeImage class FeedbackRequestTest extends TestContainersSuite { + @Mock private FeedbackRequestRepository feedbackReqRepository; + @Mock private CurrentUserServices currentUserServices; + @Mock private MemberProfileServices memberProfileServices; + @Mock private ReviewPeriodRepository reviewPeriodRepository; + @Mock private ReviewAssignmentRepository reviewAssignmentRepository; private FeedbackRequestServicesImpl feedbackRequestServices; @Inject - @Named(MailJetFactory.HTML_FORMAT) + @Named(MailJetFactory.MJML_FORMAT) private MailJetFactoryReplacement.MockEmailSender emailSender; + @Inject + private EmbeddedServer server; + + private AutoCloseable mockFinalizer; + @Inject CheckInsConfiguration checkInsConfiguration; + @BeforeAll + void initMocks() { + mockFinalizer = openMocks(this); + feedbackRequestServices = new FeedbackRequestServicesImpl(feedbackReqRepository, currentUserServices, memberProfileServices, reviewPeriodRepository, reviewAssignmentRepository, emailSender, checkInsConfiguration); + server.getApplicationContext().inject(feedbackRequestServices); + } + @BeforeEach @Tag("mocked") void setUp() { + Mockito.reset(feedbackReqRepository); + Mockito.reset(currentUserServices); + Mockito.reset(memberProfileServices); + Mockito.reset(reviewPeriodRepository); + Mockito.reset(reviewAssignmentRepository); emailSender.reset(); + } - feedbackReqRepository = Mockito.mock(FeedbackRequestRepository.class); - currentUserServices = Mockito.mock(CurrentUserServices.class); - memberProfileServices = Mockito.mock(MemberProfileServices.class); - reviewPeriodRepository = Mockito.mock(ReviewPeriodRepository.class); - reviewAssignmentRepository = Mockito.mock(ReviewAssignmentRepository.class); - feedbackRequestServices = new FeedbackRequestServicesImpl(feedbackReqRepository, currentUserServices, memberProfileServices, reviewPeriodRepository, reviewAssignmentRepository, emailSender, checkInsConfiguration); + @AfterAll + void cleanupMocks() throws Exception { + mockFinalizer.close(); } @Test @@ -250,16 +275,16 @@ void testSendSelfReviewCompletionEmailToReviewers() { feedbackRequestServices.sendSelfReviewCompletionEmailToReviewers(feedbackRequest, reviewAssignmentsSet); - assertEquals(1, emailSender.events.size()); - assertEquals( - List.of("SEND_EMAIL", "null", "null", "firstName lastName has finished their self-review for Self-Review Test.", "Self-review has been completed by firstName lastName for Self-Review Test.
PDL: PDL Profile
Supervisor: Supervisor Profile

It is now your turn in their review process. Please complete your portion in a timely manner.", reviewer01.getWorkEmail() + "," + reviewer02.getWorkEmail()), + // This should equal the number of review assignments. + assertEquals(2, emailSender.events.size()); + validateEmail("SEND_EMAIL", "null", "null", "firstName lastName has finished their self-review for Self-Review Test.", "firstName lastName has completed their self-review", emailSender.events.getFirst() ); } @Test @Tag("mocked") - void testSendSelfReviewCompletionEmailToPdlAndSupervisor() { + void testSendSelfReviewCompletionEmailToSupervisor() { UUID creatorId = UUID.randomUUID(); MemberProfile currentUser = new MemberProfile(); currentUser.setId(creatorId); @@ -297,50 +322,16 @@ void testSendSelfReviewCompletionEmailToPdlAndSupervisor() { when(memberProfileServices.getById(supervisorProfile.getId())).thenReturn(supervisorProfile); when(reviewPeriodRepository.findById(reviewPeriodId)).thenReturn(Optional.of(reviewPeriod)); - feedbackRequestServices.sendSelfReviewCompletionEmailToPdlAndSupervisor(feedbackRequest); - - assertEquals(1, emailSender.events.size()); - assertEquals( - List.of("SEND_EMAIL", "null", "null", "firstName lastName has finished their self-review for Self-Review Test.", "Self-review has been completed by firstName lastName for Self-Review Test.
PDL: PDL Profile
Supervisor: Supervisor Profile
", supervisorProfile.getWorkEmail() + "," + pdlProfile.getWorkEmail()), - emailSender.events.getFirst() - ); - } - - @Test - @Tag("mocked") - void testSendSelfReviewCompletionEmailToPdlAndSupervisor_MissingPdl() { - UUID creatorId = UUID.randomUUID(); - MemberProfile currentUser = new MemberProfile(); - currentUser.setId(creatorId); - - MemberProfile supervisorProfile = new MemberProfile(); - supervisorProfile.setId(UUID.randomUUID()); - supervisorProfile.setFirstName("Supervisor"); - supervisorProfile.setLastName("Profile"); - supervisorProfile.setWorkEmail("supervisor@example.com"); - - currentUser.setSupervisorid(supervisorProfile.getId()); - - String firstName = "firstName"; - String lastName = "lastName"; - - currentUser.setFirstName(firstName); - currentUser.setLastName(lastName); - - when(currentUserServices.getCurrentUser()).thenReturn(currentUser); - when(memberProfileServices.getById(supervisorProfile.getId())).thenReturn(supervisorProfile); + feedbackRequestServices.sendSelfReviewCompletionEmailToSupervisor(feedbackRequest); - feedbackRequestServices.sendSelfReviewCompletionEmailToPdlAndSupervisor(new FeedbackRequest()); assertEquals(1, emailSender.events.size()); - assertEquals( - List.of("SEND_EMAIL", "null", "null", "firstName lastName has finished their self-review.", "Self-review has been completed by firstName lastName.
Supervisor: Supervisor Profile
", supervisorProfile.getWorkEmail()), - emailSender.events.getFirst() + validateEmail("SEND_EMAIL", "null", "null", "firstName lastName has finished their self-review for Self-Review Test.", "firstName lastName has completed their self-review", emailSender.events.getFirst() ); } @Test @Tag("mocked") - void testSendSelfReviewCompletionEmailToPdlAndSupervisor_MissingSupervisor() { + void testSendSelfReviewCompletionEmailToSupervisor_MissingSupervisor() { UUID creatorId = UUID.randomUUID(); MemberProfile currentUser = new MemberProfile(); currentUser.setId(creatorId); @@ -362,37 +353,13 @@ void testSendSelfReviewCompletionEmailToPdlAndSupervisor_MissingSupervisor() { when(currentUserServices.getCurrentUser()).thenReturn(currentUser); when(memberProfileServices.getById(pdlProfile.getId())).thenReturn(pdlProfile); - feedbackRequestServices.sendSelfReviewCompletionEmailToPdlAndSupervisor(new FeedbackRequest()); - assertEquals(1, emailSender.events.size()); - assertEquals( - List.of("SEND_EMAIL", "null", "null", "firstName lastName has finished their self-review.", "Self-review has been completed by firstName lastName.
PDL: PDL Profile
", pdlProfile.getWorkEmail()), - emailSender.events.getFirst() - ); - } - - @Test - @Tag("mocked") - void testSendSelfReviewCompletionEmailToPdlAndSupervisor_MissingPdlAndSupervisor() { - UUID creatorId = UUID.randomUUID(); - MemberProfile currentUser = new MemberProfile(); - currentUser.setId(creatorId); - - String firstName = "firstName"; - String lastName = "lastName"; - - currentUser.setFirstName(firstName); - currentUser.setLastName(lastName); - - when(currentUserServices.getCurrentUser()).thenReturn(currentUser); - - feedbackRequestServices.sendSelfReviewCompletionEmailToPdlAndSupervisor(new FeedbackRequest()); - + feedbackRequestServices.sendSelfReviewCompletionEmailToSupervisor(new FeedbackRequest()); assertEquals(0, emailSender.events.size()); } @Test @Tag("mocked") - void testSendSelfReviewCompletionEmailToPdlAndSupervisor_EmailSenderException() { + void testSendSelfReviewCompletionEmailToSupervisor_EmailSenderException() { UUID creatorId = UUID.randomUUID(); MemberProfile currentUser = new MemberProfile(); currentUser.setId(creatorId); @@ -424,11 +391,21 @@ void testSendSelfReviewCompletionEmailToPdlAndSupervisor_EmailSenderException() emailSender.setException(new RuntimeException("Email sending failed")); - assertDoesNotThrow(() -> feedbackRequestServices.sendSelfReviewCompletionEmailToPdlAndSupervisor(new FeedbackRequest())); + assertDoesNotThrow(() -> feedbackRequestServices.sendSelfReviewCompletionEmailToSupervisor(new FeedbackRequest())); assertEquals(1, emailSender.events.size()); - assertEquals( - List.of("SEND_EMAIL", "null", "null", "firstName lastName has finished their self-review.", "Self-review has been completed by firstName lastName.
PDL: PDL Profile
Supervisor: Supervisor Profile
", supervisorProfile.getWorkEmail() + "," + pdlProfile.getWorkEmail()), - emailSender.events.getFirst() + validateEmail("SEND_EMAIL", "null", "null", "firstName lastName has finished their self-review.", "firstName lastName has completed their self-review", emailSender.events.getFirst() ); } + + private void validateEmail(String action, String fromName, + String fromAddress, String subject, + String partial, List event) { + assertEquals(action, event.get(0)); + assertEquals(fromName, event.get(1)); + assertEquals(fromAddress, event.get(2)); + assertEquals(subject, event.get(3)); + if (partial != null && !partial.isEmpty()) { + assertTrue(event.get(4).contains(partial)); + } + } } diff --git a/server/src/test/java/com/objectcomputing/checkins/services/pulse/PulseServicesTest.java b/server/src/test/java/com/objectcomputing/checkins/services/pulse/PulseServicesTest.java index 03f606bce..f6d861c7f 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/pulse/PulseServicesTest.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/pulse/PulseServicesTest.java @@ -30,11 +30,12 @@ import static com.objectcomputing.checkins.services.role.RoleType.Constants.ADMIN_ROLE; import static com.objectcomputing.checkins.services.role.RoleType.Constants.MEMBER_ROLE; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; @Property(name = "replace.mailjet.factory", value = StringUtils.TRUE) class PulseServicesTest extends TestContainersSuite implements TeamFixture, RoleFixture { @Inject - @Named(MailJetFactory.HTML_FORMAT) + @Named(MailJetFactory.MJML_FORMAT) private MailJetFactoryReplacement.MockEmailSender emailSender; @Inject @@ -98,9 +99,11 @@ void testBiWeeklySendEmail() { pulseServices.sendPendingEmail(biWeeklyDate); assertEquals(1, emailSender.events.size()); - final List event = emailSender.events.get(0); - assertEquals(6, event.size()); - assertEquals(recipients, event.get(5)); + validateEmail("SEND_EMAIL", "null", "null", + "Check Out the Pulse Survey!", + "Please fill out your Pulse survey, if you haven't already done so.", + recipients, + emailSender.events.getFirst()); } @Test @@ -111,9 +114,11 @@ void testWeeklySendEmail() { pulseServices.sendPendingEmail(weeklyDate); assertEquals(1, emailSender.events.size()); - final List event = emailSender.events.get(0); - assertEquals(6, event.size()); - assertEquals(recipients, event.get(5)); + validateEmail("SEND_EMAIL", "null", "null", + "Check Out the Pulse Survey!", + "Please fill out your Pulse survey, if you haven't already done so.", + recipients, + emailSender.events.getFirst()); } @Test @@ -124,9 +129,11 @@ void testMonthlySendEmail() { pulseServices.sendPendingEmail(monthlyDate); assertEquals(1, emailSender.events.size()); - final List event = emailSender.events.get(0); - assertEquals(6, event.size()); - assertEquals(recipients, event.get(5)); + validateEmail("SEND_EMAIL", "null", "null", + "Check Out the Pulse Survey!", + "Please fill out your Pulse survey, if you haven't already done so.", + recipients, + emailSender.events.getFirst()); } @Test @@ -154,4 +161,18 @@ void testNoSendEmail() { // This should be zero because the date is not a Monday. assertEquals(0, emailSender.events.size()); } + + private void validateEmail(String action, String fromName, + String fromAddress, String subject, + String partial, String recipients, + List event) { + assertEquals(action, event.get(0)); + assertEquals(fromName, event.get(1)); + assertEquals(fromAddress, event.get(2)); + assertEquals(subject, event.get(3)); + if (partial != null && !partial.isEmpty()) { + assertTrue(event.get(4).contains(partial)); + } + assertEquals(recipients, event.get(5)); + } }