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
@@ -0,0 +1,74 @@
package com.objectcomputing.checkins.services.pulse;

import com.objectcomputing.checkins.configuration.CheckInsConfiguration;
import com.objectcomputing.checkins.notifications.email.EmailSender;
import com.objectcomputing.checkins.notifications.email.MailJetFactory;
import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices;

import jakarta.inject.Named;

import java.util.stream.Collectors;
import java.util.List;

class PulseEmail {
private final EmailSender emailSender;
private final CheckInsConfiguration checkInsConfiguration;
private final MemberProfileServices memberProfileServices;

private final String SUBJECT = "Check Out the Pulse Survey!";

public PulseEmail(@Named(MailJetFactory.HTML_FORMAT) EmailSender emailSender,
CheckInsConfiguration checkInsConfiguration,
MemberProfileServices memberProfileServices) {
this.emailSender = emailSender;
this.checkInsConfiguration = checkInsConfiguration;
this.memberProfileServices = memberProfileServices;
}

private List<String> getActiveTeamMembers() {
List<String> profiles = memberProfileServices.findAll().stream()
.filter(p -> p.getTerminationDate() == null)
.map(p -> p.getWorkEmail())
.collect(Collectors.toList());
return profiles;
}

private String getEmailContent() {
/*
<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">Please fill out your Pulse survey, if you haven't already done so. We want to know how you're doing!</mj-text>
<mj-text font-size="16px" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" color="#4d4c4f">Click <a href="%s/pulse" target="_blank">here</a> to begin.</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
*/
return String.format("""
<html>
<body>
<div style="margin:0px auto;float: left; max-width:600px;">
<p style="border-top:solid 4px #2559a7;font-size:1px;margin:0px auto;width:100%%;"></p>
<p></p>
<div style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:left;color:#4d4c4f;">
Please fill out your Pulse survey, if you haven't already done so. We want to know how you're doing!</div>
<p></p>
<div style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:left;color:#4d4c4f;">
Click <a href="%s/pulse" target="_blank">here</a> to begin.</div>
</div>
</div>
</body>
</html>
""", checkInsConfiguration.getWebAddress());
}

public void send() {
final List<String> recipients = getActiveTeamMembers();
final String content = getEmailContent();
emailSender.sendEmail(null, null, SUBJECT, content,
recipients.toArray(new String[recipients.size()]));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.objectcomputing.checkins.services.pulse;

import java.time.LocalDate;

public interface PulseServices {
public void sendPendingEmail(LocalDate now);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.objectcomputing.checkins.services.pulse;

import com.objectcomputing.checkins.configuration.CheckInsConfiguration;
import com.objectcomputing.checkins.notifications.email.EmailSender;
import com.objectcomputing.checkins.notifications.email.MailJetFactory;
import com.objectcomputing.checkins.services.memberprofile.MemberProfileServices;
import com.objectcomputing.checkins.services.settings.SettingsServices;
import com.objectcomputing.checkins.services.settings.Setting;
import com.objectcomputing.checkins.exceptions.NotFoundException;

import lombok.Getter;
import lombok.AllArgsConstructor;

import jakarta.inject.Named;
import jakarta.inject.Singleton;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.HashMap;
import java.time.LocalDate;
import java.time.DayOfWeek;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;

@Singleton
public class PulseServicesImpl implements PulseServices {
@Getter
@AllArgsConstructor
private class Frequency {
private final int count;
private final ChronoUnit units;
}

private static final Logger LOG = LoggerFactory.getLogger(PulseServicesImpl.class);
private final EmailSender emailSender;
private final CheckInsConfiguration checkInsConfiguration;
private final MemberProfileServices memberProfileServices;
private final SettingsServices settingsServices;
private final Map<String, Boolean> sent = new HashMap<String, Boolean>();

private final DayOfWeek emailDay = DayOfWeek.MONDAY;

private String setting = "bi-weekly";
private final Map<String, Frequency> frequency = Map.of(
"weekly", new Frequency(1, ChronoUnit.WEEKS),
"bi-weekly", new Frequency(2, ChronoUnit.WEEKS),
"monthly", new Frequency(1, ChronoUnit.MONTHS)
);

public PulseServicesImpl(
@Named(MailJetFactory.HTML_FORMAT) EmailSender emailSender,
CheckInsConfiguration checkInsConfiguration,
MemberProfileServices memberProfileServices,
SettingsServices settingsServices) {
this.emailSender = emailSender;
this.checkInsConfiguration = checkInsConfiguration;
this.memberProfileServices = memberProfileServices;
this.settingsServices = settingsServices;
}

public void sendPendingEmail(LocalDate check) {
if (check.getDayOfWeek() == emailDay) {
LOG.info("Checking for pending Pulse email");
// Start from the first of the year and move forward to ensure that we
// are sending email during the correct week.
LocalDate start = check.with(TemporalAdjusters.firstDayOfYear())
.with(TemporalAdjusters.firstInMonth(emailDay));

try {
Setting freq = settingsServices.findByName("PULSE_EMAIL_FREQUENCY");
if (frequency.containsKey(freq.getValue())) {
setting = freq.getValue();
} else {
LOG.error("Invalid Pulse Email Frequency Setting: " + freq.getValue());
}
} catch(NotFoundException ex) {
// Use the default setting.
LOG.error("Pulse Frequency Error: " + ex.toString());
}

LOG.info("Using Pulse Frequency: " + setting);
final Frequency freq = frequency.get(setting);
do {
if (start.getDayOfMonth() == check.getDayOfMonth()) {
LOG.info("Check day of month matches frequency day");
final String key = new StringBuilder(start.getMonth().toString())
.append("_")
.append(String.valueOf(start.getDayOfMonth()))
.toString();
if (sent.containsKey(key)) {
LOG.info("The Pulse Email has already been sent today");
} else {
LOG.info("Sending Pulse Email");
send();
sent.put(key, true);
}
break;
}
start = start.plus(freq.getCount(), freq.getUnits());

// Apply firstInMonth(emailDay) to support adding one month to the start
// date. When adding weeks, it remains on the original day. But, when
// adding months, it can move away from the first of the month and we
// need the day specified by emailDay.
if (freq.getUnits() == ChronoUnit.MONTHS) {
start = start.with(TemporalAdjusters.firstInMonth(emailDay));
}
} while(start.isBefore(check) || start.isEqual(check));
}
}

private void send() {
PulseEmail email = new PulseEmail(emailSender, checkInsConfiguration,
memberProfileServices);
email.send();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.objectcomputing.checkins.services.feedback_request.FeedbackRequest;
import com.objectcomputing.checkins.services.feedback_request.FeedbackRequestRepository;
import com.objectcomputing.checkins.services.feedback_request.FeedbackRequestServicesImpl;
import com.objectcomputing.checkins.services.pulse.PulseServices;
import jakarta.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -16,11 +17,14 @@ public class CheckServicesImpl implements CheckServices {
private static final Logger LOG = LoggerFactory.getLogger(CheckServicesImpl.class);
private final FeedbackRequestServicesImpl feedbackRequestServices;
private final FeedbackRequestRepository feedbackRequestRepository;
private final PulseServices pulseServices;

public CheckServicesImpl(FeedbackRequestServicesImpl feedbackRequestServices,
FeedbackRequestRepository feedbackRequestRepository) {
FeedbackRequestRepository feedbackRequestRepository,
PulseServices pulseServices) {
this.feedbackRequestServices = feedbackRequestServices;
this.feedbackRequestRepository = feedbackRequestRepository;
this.pulseServices = pulseServices;
}

@Override
Expand All @@ -33,6 +37,7 @@ public boolean sendScheduledEmails() {
req.setStatus("sent");
feedbackRequestRepository.update(req);
}
pulseServices.sendPendingEmail(today);
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
@JsonSerialize(using = SettingOptionSerializer.class)
@JsonDeserialize(using = SettingOptionDeserializer.class)
public enum SettingOption {
LOGO_URL("The logo url", Category.THEME, Type.FILE);
LOGO_URL("The logo url", Category.THEME, Type.FILE),
PULSE_EMAIL_FREQUENCY("The Pulse Email Frequency (weekly, bi-weekly, monthly)", Category.CHECK_INS, Type.STRING);

private final String description;
private final Category category;
Expand All @@ -27,6 +28,18 @@ public enum SettingOption {
this.type = type;
}

public String getDescription() {
return description;
}

public Category getCategory() {
return category;
}

public Type getType() {
return type;
}

public static List<SettingOption> getOptions(){
return Arrays.asList(SettingOption.values());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.objectcomputing.checkins.services.permissions.Permission;
import com.objectcomputing.checkins.services.permissions.RequiredPermission;
import com.objectcomputing.checkins.exceptions.NotFoundException;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.annotation.*;
Expand All @@ -18,6 +19,7 @@
import java.net.URI;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

@Controller(SettingsController.PATH)
@ExecuteOn(TaskExecutors.BLOCKING)
Expand Down Expand Up @@ -66,8 +68,24 @@ public SettingsResponseDTO findByName(@PathVariable @NotNull String name) {
*/
@Get("/options")
@RequiredPermission(Permission.CAN_VIEW_SETTINGS)
public List<SettingOption> getOptions() {
return SettingOption.getOptions();
public List<SettingsResponseDTO> getOptions() {
List<SettingOption> options = SettingOption.getOptions();
return options.stream().map(option -> {
// Default to an empty value and "invalid" UUID.
// This can be used by the client to determine pre-existance.
String value = "";
UUID uuid = new UUID(0, 0);
try {
Setting s = settingsServices.findByName(option.name());
uuid = s.getId();
value = s.getValue();
} catch(NotFoundException ex) {
}
return new SettingsResponseDTO(
uuid, option.name(), option.getDescription(),
option.getCategory(), option.getType(),
value);
}).collect(Collectors.toList());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

Expand All @@ -12,6 +13,7 @@
@Setter
@Getter
@Introspected
@AllArgsConstructor
public class SettingsResponseDTO {

@NotNull
Expand All @@ -38,4 +40,6 @@ public class SettingsResponseDTO {
@Schema(description = "value of the setting")
private String value;

public SettingsResponseDTO() {
}
}
Loading