KPINotifications.java
/*
* Copyright 2024 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gringlobal.notification;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.gringlobal.model.Cooperator;
import org.gringlobal.model.SysUser;
import org.gringlobal.model.kpi.DimensionKey;
import org.gringlobal.model.kpi.ExecutionRun;
import org.gringlobal.model.kpi.QExecution;
import org.gringlobal.model.notification.NotificationSchedule;
import org.gringlobal.notification.schedule.ScheduledNotification;
import org.gringlobal.persistence.kpi.ExecutionRepository;
import org.gringlobal.persistence.kpi.ExecutionRunRepository;
import org.gringlobal.service.AppResourceService;
import org.gringlobal.service.EMailService;
import org.gringlobal.service.TemplatingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* Send the latest KPI execution results by email.
*
* The list of recipients is compiled from SIDs that are mentioned in the permissions list, permissions are not checked.
*/
@Component
@Slf4j
public class KPINotifications {
@Autowired
private ExecutionRepository executionRepository;
@Autowired
private ExecutionRunRepository executionRunRepository;
@Autowired
private EMailService emailService;
@Autowired
private TemplatingService templatingService;
@Autowired
private AppResourceService appResourceService;
@Value("${frontend.url}")
private String frontendUrl;
private static final String KPI_EXECUTION_URL = "/admin/kpi/";
@ScheduledNotification
// @SchedulerLock(name = "org.gringlobal.notification.KPINotifications")
// @Scheduled(cron = "0 0 12 ? * MON-FRI", zone = "UTC") // Execute every MON-FRI at 12:00 UTC
// @Scheduled(fixedDelay = 10000) // For testing
@Transactional(readOnly = true)
public void sendAllKPIs(NotificationSchedule schedule, List<SysUser> recipients) throws Exception {
log.info("Generating scheduled notifications for KPI runs");
var activeExecutions = executionRepository.findAll(QExecution.execution.isActive.eq("Y"));
for (var execution : activeExecutions) {
notifyLastKPIExecution(execution.getName(), recipients);
}
}
@Transactional(readOnly = true)
public void notifyLastKPIExecution(String executionName, List<SysUser> recipients) throws Exception {
var execution = executionRepository.findByName(executionName);
log.info("Generating last KPI run notification for: {} {}", execution.getName(), execution.getTitle());
var sysUserRecipients = recipients.stream()
.map(SysUser::getCooperator) // To cooperator
.filter(Objects::nonNull)
.peek(cooperator -> log.debug("Notifiying {}", cooperator))
.map(Cooperator::getEmail) // To email
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
if (sysUserRecipients.isEmpty()) return;
log.info("Sending last KPI run notification to: {}", sysUserRecipients);
var lastRuns = executionRunRepository.findLast(execution, Pageable.ofSize(1)).getContent();
if (lastRuns.isEmpty()) return;
log.info("Sending last KPI run of {} to {} emails", execution.getName(), sysUserRecipients.size());
ExecutionRun lastRun = lastRuns.get(0);
List<String> dimensionNames = new ArrayList<>();
var observations = lastRun.getObservations();
if (observations != null && !observations.isEmpty()) {
var dimensions = observations.get(0).getDimensions();
if (dimensions != null && !dimensions.isEmpty()) {
dimensionNames = dimensions.stream().map(DimensionKey::getName).collect(Collectors.toList());
}
}
Map<String, Object> templateParams = new HashMap<>();
templateParams.put("execution", execution);
templateParams.put("executionRun", lastRun);
templateParams.put("dimensionNames", dimensionNames);
templateParams.put("kpiExecutionURL", frontendUrl + KPI_EXECUTION_URL + executionName);
var resource = appResourceService.getResource(AppResourceService.APP_NAME_GGCE, "notification/kpi-execution-template.mustache", Locale.getDefault());
String template;
if (resource != null) {
template = resource.getDisplayMember();
} else {
log.info("AppResource 'notification/kpi-execution-template.mustache' not found, using bundled template");
template = Files.readString(Path.of(getClass().getResource("/notification/kpi-execution-template.mustache").getPath()));
}
var messageBody = templatingService.fillTemplate(template, templateParams);
for (var emailTo : sysUserRecipients) {
emailService.sendMail(execution.getTitle(), messageBody, emailTo);
}
}
}