package com.linkedin.thirdeye.dashboard.resources; import com.google.common.base.Strings; import com.linkedin.thirdeye.anomaly.SmtpConfiguration; import com.linkedin.thirdeye.anomaly.ThirdEyeAnomalyConfiguration; import com.linkedin.thirdeye.anomaly.alert.util.AlertFilterHelper; import com.linkedin.thirdeye.anomaly.alert.util.AnomalyReportGenerator; import com.linkedin.thirdeye.datalayer.bao.AlertConfigManager; import com.linkedin.thirdeye.datalayer.dto.AlertConfigDTO; import com.linkedin.thirdeye.datalayer.dto.MergedAnomalyResultDTO; import com.linkedin.thirdeye.detector.email.filter.AlertFilterFactory; import java.util.Arrays; import java.util.List; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import com.linkedin.thirdeye.client.DAORegistry; import com.linkedin.thirdeye.common.ThirdEyeConfiguration; import com.linkedin.thirdeye.datalayer.bao.AnomalyFunctionManager; import com.linkedin.thirdeye.datalayer.bao.EmailConfigurationManager; import com.linkedin.thirdeye.datalayer.dto.AnomalyFunctionDTO; import com.linkedin.thirdeye.datalayer.dto.EmailConfigurationDTO; import org.apache.commons.lang3.StringUtils; @Path("thirdeye/email") @Produces(MediaType.APPLICATION_JSON) public class EmailResource { private final AnomalyFunctionManager functionDAO; private final EmailConfigurationManager emailDAO; private final AlertConfigManager alertDAO; private static final DAORegistry DAO_REGISTRY = DAORegistry.getInstance(); private ThirdEyeConfiguration thirdeyeConfiguration = null; private AlertFilterFactory alertFilterFactory; public EmailResource(ThirdEyeConfiguration thirdeyeConfiguration) { this.functionDAO = DAO_REGISTRY.getAnomalyFunctionDAO(); this.emailDAO = DAO_REGISTRY.getEmailConfigurationDAO(); this.alertDAO = DAO_REGISTRY.getAlertConfigDAO(); this.thirdeyeConfiguration = thirdeyeConfiguration; this.alertFilterFactory = new AlertFilterFactory(this.thirdeyeConfiguration.getAlertFilterConfigPath()); } @POST public Response createEmailConfig(EmailConfigurationDTO emailConfiguration) { List<EmailConfigurationDTO> emails = emailDAO.findByCollectionMetric(emailConfiguration.getCollection(), emailConfiguration.getMetric()); if (emails.size() > 0) { EmailConfigurationDTO base = emails.get(0); base.setActive(true); base.setToAddresses(emailConfiguration.getToAddresses()); emailDAO.update(base); return Response.ok(base.getId()).build(); } Long id = emailDAO.save(emailConfiguration); return Response.ok(id).build(); } @GET @Path("{id}") public EmailConfigurationDTO getEmailConfigById (@PathParam("id") Long id) { return emailDAO.findById(id); } @POST @Path("alert") public Response createAlertConfig(AlertConfigDTO alertConfigDTO) { Long id = alertDAO.save(alertConfigDTO); return Response.ok(id).build(); } @GET @Path("alert/{id}") public AlertConfigDTO getAlertConfigById (@PathParam("id") Long id) { return alertDAO.findById(id); } @DELETE @Path("alert/{alertId}") public Response deleteByAlertId(@PathParam("alertId") Long alertId) { alertDAO.deleteById(alertId); return Response.ok().build(); } @GET public List<EmailConfigurationDTO> getEmailConfigurations( @QueryParam("collection") String collection, @QueryParam("metric") String metric) { if (StringUtils.isNotEmpty(collection) && StringUtils.isNotEmpty(metric)) { return emailDAO.findByCollectionMetric(collection, metric); } if (StringUtils.isNotEmpty(collection)) { return emailDAO.findByCollection(collection); } return emailDAO.findAll(); } @POST @Path("{emailId}/add/{functionId}") public void addFunctionInEmail(@PathParam("emailId") Long emailId, @PathParam("functionId") Long functionId) { AnomalyFunctionDTO function = functionDAO.findById(functionId); EmailConfigurationDTO emailConfiguration = emailDAO.findById(emailId); List<EmailConfigurationDTO> emailConfigurationsWithFunction = emailDAO.findByFunctionId(functionId); for (EmailConfigurationDTO emailConfigurationDTO : emailConfigurationsWithFunction) { emailConfigurationDTO.getFunctions().remove(function); emailDAO.update(emailConfigurationDTO); } if (function != null && emailConfiguration != null) { if (!emailConfiguration.getFunctions().contains(function)) { emailConfiguration.getFunctions().add(function); emailDAO.update(emailConfiguration); } } else { throw new IllegalArgumentException( "function or email not found for email : " + emailId + " function : " + functionId); } } @POST @Path("{emailId}/delete/{functionId}") public void removeFunctionFromEmail(@PathParam("emailId") Long emailId, @PathParam("functionId") Long functionId) { AnomalyFunctionDTO function = functionDAO.findById(functionId); EmailConfigurationDTO emailConfiguration = emailDAO.findById(emailId); if (function != null && emailConfiguration != null) { if (emailConfiguration.getFunctions().contains(function)) { emailConfiguration.getFunctions().remove(function); emailDAO.update(emailConfiguration); } } } @DELETE @Path("{emailId}") public Response deleteByEmail(@PathParam("emailId") Long emailId) { emailDAO.deleteById(emailId); return Response.ok().build(); } @GET @Path("email-config/{functionId}") public List<EmailConfigurationDTO> findEmailIdsByFunction(@PathParam("functionId") Long functionId) { return emailDAO.findByFunctionId(functionId); } // TODO : add end points for AlertConfig /** * End point to send anomalies by datasets * @param startTime start time to generate anomalies * @param endTime end time of generate anomalies * @param datasets from which datasets anomalies are from. Multiple datasets can be processed. The input should follow format dataset1,dataset2,... * @param fromAddr from which email address. Multiple email addresses can be processed. The input should follow format address1,address2,... * @param toAddr to which email address. Multiple email addresses can be processed. The input should follow format address1,address2,... * @param subject Title of the report * @param includeSentAnomaliesOnly is only include sent anomalies (which notified flag = 1) * @param isApplyFilter is apply alert filter or not * @param teHost * @param smtpHost * @param smtpPort * @return */ @GET @Path("generate/datasets/{startTime}/{endTime}") public Response generateAndSendAlertForDatasets(@PathParam("startTime") Long startTime, @PathParam("endTime") Long endTime, @QueryParam("datasets") String datasets, @QueryParam("from") String fromAddr, @QueryParam("to") String toAddr, @QueryParam("subject") String subject, @QueryParam("includeSentAnomaliesOnly") boolean includeSentAnomaliesOnly, @QueryParam("isApplyFilter") boolean isApplyFilter, @QueryParam("teHost") String teHost, @QueryParam("smtpHost") String smtpHost, @QueryParam("smtpPort") int smtpPort) { if (Strings.isNullOrEmpty(datasets)) { throw new WebApplicationException("datasets null or empty : " + datasets); } String [] dataSetArr = datasets.split(","); if (dataSetArr.length == 0) { throw new WebApplicationException("Datasets empty : " + datasets); } if (Strings.isNullOrEmpty(toAddr)) { throw new WebApplicationException("Empty : list of recipients" + toAddr); } if(Strings.isNullOrEmpty(teHost)) { throw new WebApplicationException("Invalid TE host" + teHost); } if (Strings.isNullOrEmpty(smtpHost)) { throw new WebApplicationException("invalid smtp host" + smtpHost); } AnomalyReportGenerator anomalyReportGenerator = AnomalyReportGenerator.getInstance(); List<MergedAnomalyResultDTO> anomalies = anomalyReportGenerator .getAnomaliesForDatasets(Arrays.asList(dataSetArr), startTime, endTime); if (isApplyFilter){ anomalies = AlertFilterHelper.applyFiltrationRule(anomalies, alertFilterFactory); } ThirdEyeAnomalyConfiguration configuration = new ThirdEyeAnomalyConfiguration(); SmtpConfiguration smtpConfiguration = new SmtpConfiguration(); smtpConfiguration.setSmtpHost(smtpHost); smtpConfiguration.setSmtpPort(smtpPort); configuration.setSmtpConfiguration(smtpConfiguration); configuration.setDashboardHost(teHost); configuration.setPhantomJsPath(thirdeyeConfiguration.getPhantomJsPath()); configuration.setRootDir(thirdeyeConfiguration.getRootDir()); String emailSub = Strings.isNullOrEmpty(subject) ? "Thirdeye Anomaly Report" : subject; anomalyReportGenerator .buildReport(startTime, endTime, anomalies, emailSub, configuration, includeSentAnomaliesOnly, toAddr, fromAddr, "Thirdeye Anomaly Report", true); return Response.ok().build(); } /** * End point to send anomalies by metrics * @param startTime start time to generate anomalies * @param endTime end time of generate anomalies * @param metrics from which datasets anomalies are from. Multiple datasets can be processed. The input should follow format dataset1,dataset2,... * @param fromAddr from which email address. Multiple email addresses can be processed. The input should follow format address1,address2,... * @param toAddr to which email address. Multiple email addresses can be processed. The input should follow format address1,address2,... * @param subject Title of the report * @param includeSentAnomaliesOnly is only include sent anomalies (which notified flag = 1) * @param isApplyFilter is apply alert filter or not * @param teHost * @param smtpHost * @param smtpPort * @return */ @GET @Path("generate/metrics/{startTime}/{endTime}") public Response generateAndSendAlertForMetrics( @PathParam("startTime") Long startTime, @PathParam("endTime") Long endTime, @QueryParam("metrics") String metrics, @QueryParam("from") String fromAddr, @QueryParam("to") String toAddr,@QueryParam("subject") String subject, @QueryParam("includeSentAnomaliesOnly") boolean includeSentAnomaliesOnly, @QueryParam("isApplyFilter") boolean isApplyFilter, @QueryParam("teHost") String teHost, @QueryParam("smtpHost") String smtpHost, @QueryParam("smtpPort") int smtpPort, @QueryParam("phantomJsPath") String phantomJsPath) { if (Strings.isNullOrEmpty(metrics)) { throw new WebApplicationException("metrics null or empty: " + metrics); } String [] metricsArr = metrics.split(","); if (metricsArr.length == 0) { throw new WebApplicationException("metrics empty : " + metricsArr); } if (Strings.isNullOrEmpty(toAddr)) { throw new WebApplicationException("Empty : list of recipients" + toAddr); } if(Strings.isNullOrEmpty(teHost)) { throw new WebApplicationException("Invalid TE host" + teHost); } if (Strings.isNullOrEmpty(smtpHost)) { throw new WebApplicationException("invalid smtp host" + smtpHost); } AnomalyReportGenerator anomalyReportGenerator = AnomalyReportGenerator.getInstance(); List<MergedAnomalyResultDTO> anomalies = anomalyReportGenerator .getAnomaliesForMetrics(Arrays.asList(metricsArr), startTime, endTime); if(isApplyFilter){ anomalies = AlertFilterHelper.applyFiltrationRule(anomalies, alertFilterFactory); } ThirdEyeAnomalyConfiguration configuration = new ThirdEyeAnomalyConfiguration(); SmtpConfiguration smtpConfiguration = new SmtpConfiguration(); smtpConfiguration.setSmtpHost(smtpHost); smtpConfiguration.setSmtpPort(smtpPort); configuration.setSmtpConfiguration(smtpConfiguration); configuration.setDashboardHost(teHost); configuration.setPhantomJsPath(phantomJsPath); String emailSub = Strings.isNullOrEmpty(subject) ? "Thirdeye Anomaly Report" : subject; anomalyReportGenerator .buildReport(startTime, endTime, anomalies, emailSub, configuration, includeSentAnomaliesOnly, toAddr, fromAddr, "Thirdeye Anomaly Report", true); return Response.ok().build(); } }