Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
import com.objectcomputing.checkins.services.feedback_request.FeedbackRequest;
import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository;
import com.objectcomputing.checkins.services.memberprofile.MemberProfile;
import io.micronaut.context.annotation.Value;
import io.micronaut.context.env.Environment;
import io.micronaut.core.io.Readable;
import io.micronaut.core.io.IOUtils;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import jakarta.validation.constraints.NotNull;
Expand All @@ -25,6 +28,7 @@
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.io.BufferedReader;

@Singleton
class ReviewPeriodServicesImpl implements ReviewPeriodServices {
Expand All @@ -40,12 +44,17 @@ class ReviewPeriodServicesImpl implements ReviewPeriodServices {
private final Environment environment;
private final String webAddress;

@Value("classpath:mjml/supervisor_review_assignment.mjml")
private Readable supervisorReviewAssignmentTemplate;
@Value("classpath:mjml/review_period_announcement.mjml")
private Readable reviewPeriodAnnouncementTemplate;

ReviewPeriodServicesImpl(ReviewPeriodRepository reviewPeriodRepository,
ReviewAssignmentRepository reviewAssignmentRepository,
MemberProfileRepository memberProfileRepository,
FeedbackRequestServices feedbackRequestServices,
ReviewStatusTransitionValidator reviewStatusTransitionValidator,
@Named(MailJetFactory.HTML_FORMAT) EmailSender emailSender,
@Named(MailJetFactory.MJML_FORMAT) EmailSender emailSender,
Environment environment,
CheckInsConfiguration checkInsConfiguration) {
this.reviewPeriodRepository = reviewPeriodRepository;
Expand Down Expand Up @@ -173,8 +182,20 @@ public ReviewPeriod update(@NotNull ReviewPeriod reviewPeriod) {
" does not have a self-review template.");
}

Set<String> allInvolved = new HashSet<>();
Set<UUID> selfRevieweeIds = new HashSet<>();
for (ReviewAssignment assignment : assignments) {
Optional<MemberProfile> reviewerProfile =
memberProfileRepository.findById(assignment.getReviewerId());
if (!reviewerProfile.isEmpty()) {
allInvolved.add(reviewerProfile.get().getWorkEmail());
}
Optional<MemberProfile> revieweeProfile =
memberProfileRepository.findById(assignment.getRevieweeId());
if (!revieweeProfile.isEmpty()) {
allInvolved.add(revieweeProfile.get().getWorkEmail());
}

// This person is being reviewed and will need a self-review
// request.
selfRevieweeIds.add(assignment.getRevieweeId());
Expand All @@ -197,11 +218,46 @@ period, findCreatorId(assignment.getReviewerId()),
selfReviewCloseDate);
}
}

String emailContent = constructReviewPeriodAnnouncementEmail(
period.getName(), period.getPeriodStartDate(),
period.getPeriodEndDate(), period.getLaunchDate(),
period.getSelfReviewCloseDate(), period.getCloseDate()
);
emailSender.sendEmail(null, null, "It's time for performance reviews!", emailContent, allInvolved.toArray(new String[0]));
}

return period;
}

private String dateAsString(LocalDateTime dateTime) {
String str = String.format("%s %d, %d",
dateTime.getMonth(),
dateTime.getDayOfMonth(),
dateTime.getYear());
return str.substring(0, 1) + str.substring(1).toLowerCase();
}

private String constructReviewPeriodAnnouncementEmail(
String reviewPeriodName, LocalDateTime startDate,
LocalDateTime endDate, LocalDateTime launchDate,
LocalDateTime selfReviewDate, LocalDateTime closeDate
) {
try {
return String.format(IOUtils.readText(
new BufferedReader(
reviewPeriodAnnouncementTemplate.asReader())),
reviewPeriodName, reviewPeriodName,
dateAsString(startDate), dateAsString(endDate),
dateAsString(launchDate),
dateAsString(selfReviewDate),
dateAsString(closeDate));
} catch(Exception ex) {
LOG.error(ex.toString());
return "";
}
}

private void notifyRevieweeSupervisorsByReviewPeriod(UUID reviewPeriodId, String reviewPeriodName) {
if (environment.getActiveNames().contains(Environments.LOCAL)) return;
Set<ReviewAssignment> reviewAssignments = reviewAssignmentRepository.findByReviewPeriodId(reviewPeriodId);
Expand All @@ -217,14 +273,20 @@ private void notifyRevieweeSupervisorsByReviewPeriod(UUID reviewPeriodId, String
List<String> supervisorEmails = memberProfileRepository.findWorkEmailByIdIn(supervisorIdsToString);

// send notification to supervisors
String emailContent = constructEmailContent(reviewPeriodId, reviewPeriodName);
String emailContent = constructSupervisorEmail(reviewPeriodId, reviewPeriodName);
emailSender.sendEmail(null, null, "Review Assignments Awaiting Approval", emailContent, supervisorEmails.toArray(new String[0]));
}

private String constructEmailContent (UUID reviewPeriodId, String reviewPeriodName){
return """
<h3>Review Assignments for Review Period '%s' are ready for your approval.</h3>\
<a href="%s/feedback/reviews?period=%s">Click here</a> to review and approve reviewer assignments in the Check-Ins app.""".formatted(reviewPeriodName, webAddress, reviewPeriodId);
private String constructSupervisorEmail(UUID reviewPeriodId, String reviewPeriodName){
try {
return String.format(IOUtils.readText(
new BufferedReader(
supervisorReviewAssignmentTemplate.asReader())),
reviewPeriodName, webAddress, reviewPeriodId);
} catch(Exception ex) {
LOG.error(ex.toString());
return "";
}
}

private void validateDates(ReviewPeriod period) {
Expand Down
40 changes: 40 additions & 0 deletions server/src/main/resources/mjml/review_period_announcement.mjml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<mjml>
<mj-head>
<mj-title>%s</mj-title>
<mj-preview>It's time for performance reviews!</mj-preview>
<mj-attributes>
<mj-class name="preheader" color="#000000" font-size="11px" font-family="Ubuntu, Helvetica, Arial, sans-serif" padding="0px"></mj-class>
<mj-section background-color="#ffffff" padding="20px 20px">
</mj-attributes>
</mj-head>
<mj-body background-color="#e0f2ff">
<mj-section background-color="#2559a7">
<mj-column>
<mj-image src="https://objectcomputing.com/files/6416/4277/8012/ObjectComputingLogo_version2_white.png" alt="logo" width="150px"></mj-image>
</mj-column>
</mj-section>
<mj-hero mode="fluid-height" background-url="https://lh3.googleusercontent.com/pw/AL9nZEW6o0zSXFq6gKMdO2kpj_KZa8NLCnGq8bPoyiDFKimEuXOeuo1FsE8MhLP7ZTrSIUuf3zG4WuAQE2qLPzyDSqGAYaOhHOuWpWg2ocTjXLTbU5wUe-69JFoWbFU0gW3yDV7X5yfMollI5rq7N24vUs82=w1200-h600-no" background-color="#FFF" padding="100px 0px">
<mj-text padding="20px" font-family="Helvetica" align="center" font-size="45px" line-height="45px" font-weight="900">It's Time for Performance Reviews!</mj-text>
</mj-hero>
<mj-section background-color="#ffffff">
<mj-column>
<mj-text>
<h2>%s Starts Soon!</h2>
</mj-text>
<mj-text font-size="16px">It's nearly time for our next performance reviews. This review covers the period from %s through %s</mj-text>
<mj-text font-size="16px">Here is what to expect:
<ul>
<li><strong>%s</strong> - Self-reviews begin</li>
<li><strong>%s</strong> - Self-reviews must be completed</li>
<li><strong>%s</strong> - Reviews must be completed</li>
</ul>
</mj-text>
<mj-text font-size="16px">Help us make this a valuable experience for everyone!</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#feb672" padding="10px">
<mj-column vertical-align="top" width="100%%">
<mj-text align="center" color="#FFF" font-size="16px">Thank you for everything you do!</mj-text>
</mj-column>
</mj-section>
</mj-body>
13 changes: 13 additions & 0 deletions server/src/main/resources/mjml/supervisor_review_assignment.mjml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-divider border-color="#2559a7"></mj-divider>
<mj-text font-size="16px" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" color="#4d4c4f">
<h3>Review Assignments for Review Period '%s' are ready for your approval.</h3></mj-text>
<mj-text font-size="16px" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" color="#4d4c4f">
Click <a href="%s/feedback/reviews?period=%s">here</a> to review and approve reviewer assignments in the Check-Ins app.</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.objectcomputing.checkins.services;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.List;

public class EmailHelper {
public static void validateEmail(String action, String fromName,
String fromAddress, String subject,
String partialBody, String recipients,
List<String> event) {
assertEquals(6, event.size());
assertEquals(action, event.get(0));
assertEquals(fromName, event.get(1));
assertEquals(fromAddress, event.get(2));
assertEquals(subject, event.get(3));
if (partialBody != null && !partialBody.isEmpty()) {
assertTrue(event.get(4).contains(partialBody));
}
if (recipients != null && !recipients.isEmpty()) {
assertEquals(recipients, event.get(5));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.objectcomputing.checkins.services.memberprofile.MemberProfile;
import com.objectcomputing.checkins.services.reviews.ReviewPeriod;
import com.objectcomputing.checkins.services.role.RoleType;
import com.objectcomputing.checkins.services.EmailHelper;
import com.objectcomputing.checkins.util.Util;
import io.micronaut.context.annotation.Property;
import io.micronaut.core.type.Argument;
Expand Down Expand Up @@ -187,13 +188,13 @@ void testCreateFeedbackRequestSendsEmailNow() {
//verify appropriate email was sent
assertTrue(response.getBody().isPresent());
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()
EmailHelper.validateEmail("SEND_EMAIL",
fromName,
pdlMemberProfile.getWorkEmail(),
checkInsConfiguration.getApplication().getFeedback().getRequestSubject(),
"You have received a feedback request",
recipient.getWorkEmail(),
emailSender.events.getFirst()
);
}

Expand Down Expand Up @@ -1049,13 +1050,13 @@ void testFeedbackRequestEnableEditsSendsEmail() {
// Verify appropriate email was sent
assertTrue(response.getBody().isPresent());
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()
EmailHelper.validateEmail("SEND_EMAIL",
fromName,
pdl.getWorkEmail(),
checkInsConfiguration.getApplication().getFeedback().getRequestSubject(),
"You have received edit access to a feedback request",
recipient.getWorkEmail(),
emailSender.events.getFirst()
);
}

Expand Down Expand Up @@ -1414,18 +1415,4 @@ 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<String> 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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.objectcomputing.checkins.services.reviews.ReviewAssignmentRepository;
import com.objectcomputing.checkins.services.reviews.ReviewPeriod;
import com.objectcomputing.checkins.services.reviews.ReviewPeriodRepository;
import com.objectcomputing.checkins.services.EmailHelper;
import io.micronaut.context.annotation.Property;
import io.micronaut.core.util.StringUtils;
import io.micronaut.runtime.server.EmbeddedServer;
Expand Down Expand Up @@ -276,10 +277,10 @@ void testSendSelfReviewCompletionEmailToReviewers() {
feedbackRequestServices.sendSelfReviewCompletionEmailToReviewers(feedbackRequest, reviewAssignmentsSet);

// This should equal the number of review assignments.
// The order in which emails are sent is random. We will not be
// checking the recipient.
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()
);
EmailHelper.validateEmail("SEND_EMAIL", "null", "null", "firstName lastName has finished their self-review for Self-Review Test.", "firstName lastName has completed their self-review", null, emailSender.events.getFirst());
}

@Test
Expand Down Expand Up @@ -325,8 +326,7 @@ void testSendSelfReviewCompletionEmailToSupervisor() {
feedbackRequestServices.sendSelfReviewCompletionEmailToSupervisor(feedbackRequest);

assertEquals(1, 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()
);
EmailHelper.validateEmail("SEND_EMAIL", "null", "null", "firstName lastName has finished their self-review for Self-Review Test.", "firstName lastName has completed their self-review", supervisorProfile.getWorkEmail(), emailSender.events.getFirst());
}

@Test
Expand Down Expand Up @@ -393,19 +393,6 @@ void testSendSelfReviewCompletionEmailToSupervisor_EmailSenderException() {

assertDoesNotThrow(() -> feedbackRequestServices.sendSelfReviewCompletionEmailToSupervisor(new FeedbackRequest()));
assertEquals(1, emailSender.events.size());
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<String> 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));
}
EmailHelper.validateEmail("SEND_EMAIL", "null", "null", "firstName lastName has finished their self-review.", "firstName lastName has completed their self-review", supervisorProfile.getWorkEmail(), emailSender.events.getFirst());
}
}
Loading