Skip to content

Commit 5fcb464

Browse files
authored
Merge pull request #2628 from objectcomputing/feature-2543/review-launch-notification
Feature 2543/review launch notification
2 parents 853d207 + 839846c commit 5fcb464

File tree

8 files changed

+199
-86
lines changed

8 files changed

+199
-86
lines changed

server/src/main/java/com/objectcomputing/checkins/services/reviews/ReviewPeriodServicesImpl.java

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
import com.objectcomputing.checkins.services.feedback_request.FeedbackRequest;
1111
import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository;
1212
import com.objectcomputing.checkins.services.memberprofile.MemberProfile;
13+
import io.micronaut.context.annotation.Value;
1314
import io.micronaut.context.env.Environment;
15+
import io.micronaut.core.io.Readable;
16+
import io.micronaut.core.io.IOUtils;
1417
import jakarta.inject.Named;
1518
import jakarta.inject.Singleton;
1619
import jakarta.validation.constraints.NotNull;
@@ -25,6 +28,7 @@
2528
import java.util.Set;
2629
import java.util.UUID;
2730
import java.util.stream.Collectors;
31+
import java.io.BufferedReader;
2832

2933
@Singleton
3034
class ReviewPeriodServicesImpl implements ReviewPeriodServices {
@@ -40,12 +44,17 @@ class ReviewPeriodServicesImpl implements ReviewPeriodServices {
4044
private final Environment environment;
4145
private final String webAddress;
4246

47+
@Value("classpath:mjml/supervisor_review_assignment.mjml")
48+
private Readable supervisorReviewAssignmentTemplate;
49+
@Value("classpath:mjml/review_period_announcement.mjml")
50+
private Readable reviewPeriodAnnouncementTemplate;
51+
4352
ReviewPeriodServicesImpl(ReviewPeriodRepository reviewPeriodRepository,
4453
ReviewAssignmentRepository reviewAssignmentRepository,
4554
MemberProfileRepository memberProfileRepository,
4655
FeedbackRequestServices feedbackRequestServices,
4756
ReviewStatusTransitionValidator reviewStatusTransitionValidator,
48-
@Named(MailJetFactory.HTML_FORMAT) EmailSender emailSender,
57+
@Named(MailJetFactory.MJML_FORMAT) EmailSender emailSender,
4958
Environment environment,
5059
CheckInsConfiguration checkInsConfiguration) {
5160
this.reviewPeriodRepository = reviewPeriodRepository;
@@ -173,8 +182,20 @@ public ReviewPeriod update(@NotNull ReviewPeriod reviewPeriod) {
173182
" does not have a self-review template.");
174183
}
175184

185+
Set<String> allInvolved = new HashSet<>();
176186
Set<UUID> selfRevieweeIds = new HashSet<>();
177187
for (ReviewAssignment assignment : assignments) {
188+
Optional<MemberProfile> reviewerProfile =
189+
memberProfileRepository.findById(assignment.getReviewerId());
190+
if (!reviewerProfile.isEmpty()) {
191+
allInvolved.add(reviewerProfile.get().getWorkEmail());
192+
}
193+
Optional<MemberProfile> revieweeProfile =
194+
memberProfileRepository.findById(assignment.getRevieweeId());
195+
if (!revieweeProfile.isEmpty()) {
196+
allInvolved.add(revieweeProfile.get().getWorkEmail());
197+
}
198+
178199
// This person is being reviewed and will need a self-review
179200
// request.
180201
selfRevieweeIds.add(assignment.getRevieweeId());
@@ -197,11 +218,46 @@ period, findCreatorId(assignment.getReviewerId()),
197218
selfReviewCloseDate);
198219
}
199220
}
221+
222+
String emailContent = constructReviewPeriodAnnouncementEmail(
223+
period.getName(), period.getPeriodStartDate(),
224+
period.getPeriodEndDate(), period.getLaunchDate(),
225+
period.getSelfReviewCloseDate(), period.getCloseDate()
226+
);
227+
emailSender.sendEmail(null, null, "It's time for performance reviews!", emailContent, allInvolved.toArray(new String[0]));
200228
}
201229

202230
return period;
203231
}
204232

233+
private String dateAsString(LocalDateTime dateTime) {
234+
String str = String.format("%s %d, %d",
235+
dateTime.getMonth(),
236+
dateTime.getDayOfMonth(),
237+
dateTime.getYear());
238+
return str.substring(0, 1) + str.substring(1).toLowerCase();
239+
}
240+
241+
private String constructReviewPeriodAnnouncementEmail(
242+
String reviewPeriodName, LocalDateTime startDate,
243+
LocalDateTime endDate, LocalDateTime launchDate,
244+
LocalDateTime selfReviewDate, LocalDateTime closeDate
245+
) {
246+
try {
247+
return String.format(IOUtils.readText(
248+
new BufferedReader(
249+
reviewPeriodAnnouncementTemplate.asReader())),
250+
reviewPeriodName, reviewPeriodName,
251+
dateAsString(startDate), dateAsString(endDate),
252+
dateAsString(launchDate),
253+
dateAsString(selfReviewDate),
254+
dateAsString(closeDate));
255+
} catch(Exception ex) {
256+
LOG.error(ex.toString());
257+
return "";
258+
}
259+
}
260+
205261
private void notifyRevieweeSupervisorsByReviewPeriod(UUID reviewPeriodId, String reviewPeriodName) {
206262
if (environment.getActiveNames().contains(Environments.LOCAL)) return;
207263
Set<ReviewAssignment> reviewAssignments = reviewAssignmentRepository.findByReviewPeriodId(reviewPeriodId);
@@ -217,14 +273,20 @@ private void notifyRevieweeSupervisorsByReviewPeriod(UUID reviewPeriodId, String
217273
List<String> supervisorEmails = memberProfileRepository.findWorkEmailByIdIn(supervisorIdsToString);
218274

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

224-
private String constructEmailContent (UUID reviewPeriodId, String reviewPeriodName){
225-
return """
226-
<h3>Review Assignments for Review Period '%s' are ready for your approval.</h3>\
227-
<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);
280+
private String constructSupervisorEmail(UUID reviewPeriodId, String reviewPeriodName){
281+
try {
282+
return String.format(IOUtils.readText(
283+
new BufferedReader(
284+
supervisorReviewAssignmentTemplate.asReader())),
285+
reviewPeriodName, webAddress, reviewPeriodId);
286+
} catch(Exception ex) {
287+
LOG.error(ex.toString());
288+
return "";
289+
}
228290
}
229291

230292
private void validateDates(ReviewPeriod period) {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<mjml>
2+
<mj-head>
3+
<mj-title>%s</mj-title>
4+
<mj-preview>It's time for performance reviews!</mj-preview>
5+
<mj-attributes>
6+
<mj-class name="preheader" color="#000000" font-size="11px" font-family="Ubuntu, Helvetica, Arial, sans-serif" padding="0px"></mj-class>
7+
<mj-section background-color="#ffffff" padding="20px 20px">
8+
</mj-attributes>
9+
</mj-head>
10+
<mj-body background-color="#e0f2ff">
11+
<mj-section background-color="#2559a7">
12+
<mj-column>
13+
<mj-image src="https://objectcomputing.com/files/6416/4277/8012/ObjectComputingLogo_version2_white.png" alt="logo" width="150px"></mj-image>
14+
</mj-column>
15+
</mj-section>
16+
<mj-hero mode="fluid-height" background-url="https://lh3.googleusercontent.com/pw/AL9nZEW6o0zSXFq6gKMdO2kpj_KZa8NLCnGq8bPoyiDFKimEuXOeuo1FsE8MhLP7ZTrSIUuf3zG4WuAQE2qLPzyDSqGAYaOhHOuWpWg2ocTjXLTbU5wUe-69JFoWbFU0gW3yDV7X5yfMollI5rq7N24vUs82=w1200-h600-no" background-color="#FFF" padding="100px 0px">
17+
<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>
18+
</mj-hero>
19+
<mj-section background-color="#ffffff">
20+
<mj-column>
21+
<mj-text>
22+
<h2>%s Starts Soon!</h2>
23+
</mj-text>
24+
<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>
25+
<mj-text font-size="16px">Here is what to expect:
26+
<ul>
27+
<li><strong>%s</strong> - Self-reviews begin</li>
28+
<li><strong>%s</strong> - Self-reviews must be completed</li>
29+
<li><strong>%s</strong> - Reviews must be completed</li>
30+
</ul>
31+
</mj-text>
32+
<mj-text font-size="16px">Help us make this a valuable experience for everyone!</mj-text>
33+
</mj-column>
34+
</mj-section>
35+
<mj-section background-color="#feb672" padding="10px">
36+
<mj-column vertical-align="top" width="100%%">
37+
<mj-text align="center" color="#FFF" font-size="16px">Thank you for everything you do!</mj-text>
38+
</mj-column>
39+
</mj-section>
40+
</mj-body>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<mjml>
2+
<mj-body>
3+
<mj-section>
4+
<mj-column>
5+
<mj-divider border-color="#2559a7"></mj-divider>
6+
<mj-text font-size="16px" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" color="#4d4c4f">
7+
<h3>Review Assignments for Review Period '%s' are ready for your approval.</h3></mj-text>
8+
<mj-text font-size="16px" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" color="#4d4c4f">
9+
Click <a href="%s/feedback/reviews?period=%s">here</a> to review and approve reviewer assignments in the Check-Ins app.</mj-text>
10+
</mj-column>
11+
</mj-section>
12+
</mj-body>
13+
</mjml>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.objectcomputing.checkins.services;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import java.util.List;
7+
8+
public class EmailHelper {
9+
public static void validateEmail(String action, String fromName,
10+
String fromAddress, String subject,
11+
String partialBody, String recipients,
12+
List<String> event) {
13+
assertEquals(6, event.size());
14+
assertEquals(action, event.get(0));
15+
assertEquals(fromName, event.get(1));
16+
assertEquals(fromAddress, event.get(2));
17+
assertEquals(subject, event.get(3));
18+
if (partialBody != null && !partialBody.isEmpty()) {
19+
assertTrue(event.get(4).contains(partialBody));
20+
}
21+
if (recipients != null && !recipients.isEmpty()) {
22+
assertEquals(recipients, event.get(5));
23+
}
24+
}
25+
}

server/src/test/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestControllerTest.java

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import com.objectcomputing.checkins.services.memberprofile.MemberProfile;
1515
import com.objectcomputing.checkins.services.reviews.ReviewPeriod;
1616
import com.objectcomputing.checkins.services.role.RoleType;
17+
import com.objectcomputing.checkins.services.EmailHelper;
1718
import com.objectcomputing.checkins.util.Util;
1819
import io.micronaut.context.annotation.Property;
1920
import io.micronaut.core.type.Argument;
@@ -187,13 +188,13 @@ void testCreateFeedbackRequestSendsEmailNow() {
187188
//verify appropriate email was sent
188189
assertTrue(response.getBody().isPresent());
189190
assertEquals(1, emailSender.events.size());
190-
validateEmail("SEND_EMAIL",
191-
fromName,
192-
pdlMemberProfile.getWorkEmail(),
193-
checkInsConfiguration.getApplication().getFeedback().getRequestSubject(),
194-
"You have received a feedback request",
195-
recipient.getWorkEmail(),
196-
emailSender.events.getFirst()
191+
EmailHelper.validateEmail("SEND_EMAIL",
192+
fromName,
193+
pdlMemberProfile.getWorkEmail(),
194+
checkInsConfiguration.getApplication().getFeedback().getRequestSubject(),
195+
"You have received a feedback request",
196+
recipient.getWorkEmail(),
197+
emailSender.events.getFirst()
197198
);
198199
}
199200

@@ -1049,13 +1050,13 @@ void testFeedbackRequestEnableEditsSendsEmail() {
10491050
// Verify appropriate email was sent
10501051
assertTrue(response.getBody().isPresent());
10511052
assertEquals(1, emailSender.events.size());
1052-
validateEmail("SEND_EMAIL",
1053-
fromName,
1054-
pdl.getWorkEmail(),
1055-
checkInsConfiguration.getApplication().getFeedback().getRequestSubject(),
1056-
"You have received edit access to a feedback request",
1057-
recipient.getWorkEmail(),
1058-
emailSender.events.getFirst()
1053+
EmailHelper.validateEmail("SEND_EMAIL",
1054+
fromName,
1055+
pdl.getWorkEmail(),
1056+
checkInsConfiguration.getApplication().getFeedback().getRequestSubject(),
1057+
"You have received edit access to a feedback request",
1058+
recipient.getWorkEmail(),
1059+
emailSender.events.getFirst()
10591060
);
10601061
}
10611062

@@ -1414,18 +1415,4 @@ void testGetMultipleRequesteesBySupervisor() {
14141415
assertResponseEqualsEntity(feedbackReq, response.getBody().get().get(0));
14151416
assertResponseEqualsEntity(feedbackReqTwo, response.getBody().get().get(1));
14161417
}
1417-
1418-
private void validateEmail(String action, String fromName,
1419-
String fromAddress, String subject,
1420-
String partial, String toAddress,
1421-
List<String> event) {
1422-
assertEquals(action, event.get(0));
1423-
assertEquals(fromName, event.get(1));
1424-
assertEquals(fromAddress, event.get(2));
1425-
assertEquals(subject, event.get(3));
1426-
if (partial != null && !partial.isEmpty()) {
1427-
assertTrue(event.get(4).contains(partial));
1428-
}
1429-
assertEquals(toAddress, event.get(5));
1430-
}
14311418
}

server/src/test/java/com/objectcomputing/checkins/services/feedback_request/FeedbackRequestTest.java

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.objectcomputing.checkins.services.reviews.ReviewAssignmentRepository;
1414
import com.objectcomputing.checkins.services.reviews.ReviewPeriod;
1515
import com.objectcomputing.checkins.services.reviews.ReviewPeriodRepository;
16+
import com.objectcomputing.checkins.services.EmailHelper;
1617
import io.micronaut.context.annotation.Property;
1718
import io.micronaut.core.util.StringUtils;
1819
import io.micronaut.runtime.server.EmbeddedServer;
@@ -276,10 +277,10 @@ void testSendSelfReviewCompletionEmailToReviewers() {
276277
feedbackRequestServices.sendSelfReviewCompletionEmailToReviewers(feedbackRequest, reviewAssignmentsSet);
277278

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

285286
@Test
@@ -325,8 +326,7 @@ void testSendSelfReviewCompletionEmailToSupervisor() {
325326
feedbackRequestServices.sendSelfReviewCompletionEmailToSupervisor(feedbackRequest);
326327

327328
assertEquals(1, emailSender.events.size());
328-
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()
329-
);
329+
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());
330330
}
331331

332332
@Test
@@ -393,19 +393,6 @@ void testSendSelfReviewCompletionEmailToSupervisor_EmailSenderException() {
393393

394394
assertDoesNotThrow(() -> feedbackRequestServices.sendSelfReviewCompletionEmailToSupervisor(new FeedbackRequest()));
395395
assertEquals(1, emailSender.events.size());
396-
validateEmail("SEND_EMAIL", "null", "null", "firstName lastName has finished their self-review.", "firstName lastName has completed their self-review", emailSender.events.getFirst()
397-
);
398-
}
399-
400-
private void validateEmail(String action, String fromName,
401-
String fromAddress, String subject,
402-
String partial, List<String> event) {
403-
assertEquals(action, event.get(0));
404-
assertEquals(fromName, event.get(1));
405-
assertEquals(fromAddress, event.get(2));
406-
assertEquals(subject, event.get(3));
407-
if (partial != null && !partial.isEmpty()) {
408-
assertTrue(event.get(4).contains(partial));
409-
}
396+
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());
410397
}
411398
}

0 commit comments

Comments
 (0)