Skip to content

Commit

Permalink
20282 reduce frequency of email notifications per event (#174)
Browse files Browse the repository at this point in the history
* 20282 reduce frequency of email notifications per event

* 20282 log when email notification is skipped

* 20282 update comment

* 20282 refactor changes
  • Loading branch information
palina-krukovich authored Dec 4, 2024
1 parent 10b4447 commit 02a5898
Showing 1 changed file with 34 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.wololo.geojson.Feature;
Expand All @@ -20,6 +21,7 @@
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Component
Expand All @@ -33,6 +35,14 @@ public class EmailNotificationService extends NotificationService {
private final LayersApiService layersApiService;
private final GeometryTransformer geometryTransformer;

// ConcurrentHashMap is used because the implementation of Guava and Caffeine cache has bugs,
// which cause race conditions and can lead to incorrect behavior.
// For our simple case, the map with a manual cleaning function should be enough.
private final ConcurrentHashMap<UUID, Long> notificationTimestamps = new ConcurrentHashMap<>();

@Value("${notifications.emailNotificationsFrequencyMs}")
private long emailNotificationsFrequencyMs;

@Value("${notifications.relevantLocationsLayer}")
private String relevantLocationsLayer;

Expand All @@ -54,6 +64,11 @@ public void process(EventApiEventDto event, Map<String, Object> urbanPopulationP
LOG.info("Found new event, sending email notification. Event ID = '{}', name = '{}'", event.getEventId(), event.getName());

try {
if (exceedsNotificationFrequencyLimit(event.getEventId())) {
LOG.info("Skipped email notification. Event ID = '{}', name = '{}'", event.getEventId(), event.getName());
return;
}

List<Partner> partners = getPartners(getRelevantLocations(event.getGeometries()));
EmailDto emailDto = emailMessageFormatter.format(event, urbanPopulationProperties, analytics, partners);
emailSender.send(emailDto);
Expand Down Expand Up @@ -88,4 +103,23 @@ private List<Feature> getRelevantLocations(FeatureCollection fc) {
Geometry geometry = geometryTransformer.makeValid(geometryTransformer.getGeometryFromGeoJson(fc));
return layersApiService.getFeatures(geometry, relevantLocationsLayer, UUID.fromString(relevantLocationsLayerAppId));
}

private boolean exceedsNotificationFrequencyLimit(UUID eventId) {
long now = System.currentTimeMillis();
Long lastNotificationTime = notificationTimestamps.get(eventId);
if (lastNotificationTime != null && (now - lastNotificationTime) < emailNotificationsFrequencyMs)
return true;
notificationTimestamps.put(eventId, now);
return false;
}

/**
* Periodically cleans up old entries from the map to save space.
* Removes records where the last notification was sent more than the configured amount of time ago.
*/
@Scheduled(fixedRate = 600000)
public void cleanupOldEntries() {
long now = System.currentTimeMillis();
notificationTimestamps.entrySet().removeIf(entry -> (now - entry.getValue()) > emailNotificationsFrequencyMs);
}
}

0 comments on commit 02a5898

Please sign in to comment.