implement weekly report sending

This commit is contained in:
f43nd1r 2019-02-20 01:02:05 +01:00
parent 9c3ac2c6f2
commit b9f74d1f0f
9 changed files with 155 additions and 62 deletions

View file

@ -70,6 +70,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'mysql:mysql-connector-java'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.liquibase:liquibase-core'
implementation 'org.yaml:snakeyaml'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

View file

@ -19,19 +19,21 @@ package com.faendir.acra;
import com.faendir.acra.config.AcraConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
@PropertySource("classpath:default.properties")
@PropertySource(value = "file:${user.home}/.config/acrarium/application.properties", ignoreResourceNotFound = true)
@PropertySource(value = "file:${user.home}/.acra/application.properties", ignoreResourceNotFound = true)
@EnableConfigurationProperties(AcraConfiguration.class)
@Import(MailSenderAutoConfiguration.class)
public class BackendApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(BackendApplication.class, args);

View file

@ -23,10 +23,9 @@ import org.springframework.data.annotation.PersistenceConstructor;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import java.io.Serializable;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.Objects;
/**
@ -43,11 +42,12 @@ public class MailSettings {
@Id
@ManyToOne
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "username")
private User user;
private SendMode sendMode;
private boolean all;
private boolean newBug;
private boolean regression;
private boolean spike;
private boolean summary;
@PersistenceConstructor
MailSettings() {
@ -61,37 +61,20 @@ public class MailSettings {
return user;
}
public SendMode getSendMode() {
return sendMode;
}
public boolean isAll() {
return all;
}
public boolean isNewBug() {
public boolean getNewBug() {
return newBug;
}
public boolean isRegression() {
public boolean getRegression() {
return regression;
}
public enum SendMode {
OFF(null),
INSTANT(null),
HOURLY(ChronoUnit.HOURS),
DAILY(ChronoUnit.DAYS),
WEEKLY(ChronoUnit.WEEKS);
private final TemporalUnit unit;
public boolean getSpike() {
return spike;
}
SendMode(TemporalUnit unit) {
this.unit = unit;
}
public TemporalUnit getUnit() {
return unit;
}
public boolean getSummary() {
return summary;
}
static class ID implements Serializable {

View file

@ -49,6 +49,7 @@ public class Stacktrace {
private Bug bug;
@Type(type = "text") private String stacktrace;
@ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, optional = false, fetch = FetchType.EAGER)
@OnDelete(action = OnDeleteAction.CASCADE)
private Version version;
@PersistenceConstructor

View file

@ -48,6 +48,7 @@ public class User implements UserDetails {
@ElementCollection(fetch = FetchType.EAGER)
private Set<Permission> permissions;
private String password;
private String mail;
@PersistenceConstructor
User() {
@ -112,6 +113,15 @@ public class User implements UserDetails {
return true;
}
public String getMail() {
return mail;
}
public void setMail(String mail) {
this.mail = mail;
}
public enum Role implements GrantedAuthority {
ADMIN,
USER,

View file

@ -16,29 +16,42 @@
package com.faendir.acra.service;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.model.Bug;
import com.faendir.acra.model.MailSettings;
import com.faendir.acra.model.Report;
import com.faendir.acra.model.Stacktrace;
import com.faendir.acra.model.Version;
import com.faendir.acra.model.QBug;
import com.faendir.acra.model.User;
import com.faendir.acra.ui.view.bug.tabs.ReportTab;
import com.querydsl.core.Tuple;
import com.querydsl.jpa.impl.JPAQuery;
import com.vaadin.flow.i18n.I18NProvider;
import com.vaadin.flow.router.RouteConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.event.EventListener;
import org.springframework.lang.NonNull;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.persistence.EntityManager;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import static com.faendir.acra.model.QBug.bug;
import static com.faendir.acra.model.QMailSettings.mailSettings;
import static com.faendir.acra.model.QReport.report;
import static com.faendir.acra.model.QStacktrace.stacktrace1;
/**
* @author lukas
@ -47,48 +60,55 @@ import static com.faendir.acra.model.QReport.report;
@Service
@EnableScheduling
@EnableAsync
@ConditionalOnProperty(prefix = "spring.mail", name = "host")
public class MailService {
private final EntityManager entityManager;
@NonNull
private final I18NProvider i18nProvider;
@NonNull
private final JavaMailSender mailSender;
public MailService(@NonNull EntityManager entityManager) {
public MailService(@NonNull EntityManager entityManager, @NonNull I18NProvider i18nProvider, @NonNull JavaMailSender mailSender) {
this.entityManager = entityManager;
this.i18nProvider = i18nProvider;
this.mailSender = mailSender;
}
@EventListener
@Async
public void onNewReport(NewReportEvent event) {
}
@Scheduled(cron = "0 0 * * * *")
public void checkHourly() {
check(MailSettings.SendMode.HOURLY);
}
@Scheduled(cron = "0 0 0 * * *")
public void checkDaily() {
check(MailSettings.SendMode.DAILY);
}
@Scheduled(cron = "0 0 0 * * SUN")
public void checkWeekly() {
check(MailSettings.SendMode.WEEKLY);
}
private void check(MailSettings.SendMode sendMode) {
List<MailSettings> settings = new JPAQuery<>(entityManager).select(mailSettings).from(mailSettings).where(mailSettings.sendMode.eq(sendMode)).fetch();
Map<App, List<MailSettings>> appMap = settings.stream().collect(Collectors.groupingBy(MailSettings::getApp));
for (Map.Entry<App, List<MailSettings>> appEntry : appMap.entrySet()) {
List<Report> reports = new JPAQuery<>(entityManager).select(report).where(report.date.after(ZonedDateTime.now().minus(1, sendMode.getUnit())).and(report.stacktrace.bug.app.eq(appEntry.getKey()))).fetch();
if(!reports.isEmpty()) {
Map<Bug, List<Report>> bugMap = reports.stream().collect(Collectors.groupingBy(r -> r.getStacktrace().getBug()));
for (Map.Entry<Bug, List<Report>> bugEntry : bugMap.entrySet()) {
boolean newBug = new JPAQuery<>().select(report).where(report.stacktrace.bug.eq(bugEntry.getKey()).and(report.notIn(bugEntry.getValue()))).fetchFirst() == null;
if (!newBug && bugEntry.getKey().getSolvedVersion() != null) {
int maxNewVersion = reports.stream().map(Report::getStacktrace).map(Stacktrace::getVersion).mapToInt(Version::getCode).max().orElseThrow(IllegalStateException::new);
boolean regression = new JPAQuery<>().select(report).where(report.stacktrace.bug.eq(bugEntry.getKey()).and(report.notIn(bugEntry.getValue())).and(report.stacktrace.version.code.goe(maxNewVersion))).fetchFirst() == null;
public void weeklyReport() {
Map<App, List<MailSettings>> settings = new JPAQuery<>(entityManager).select(mailSettings).from(mailSettings).where(mailSettings.summary.isTrue()).fetch()
.stream()
.collect(Collectors.groupingBy(MailSettings::getApp, Collectors.toList()));
RouteConfiguration configuration = RouteConfiguration.forApplicationScope();
for (Map.Entry<App, List<MailSettings>> entry : settings.entrySet()) {
List<Tuple> tuples = new JPAQuery<>(entityManager).from(bug).join(stacktrace1).on(bug.eq(stacktrace1.bug)).join(report).on(stacktrace1.eq(report.stacktrace)).where(bug.app.eq(entry.getKey()).and(report.date.after(ZonedDateTime.now().minus(1, ChronoUnit.WEEKS))))
.groupBy(bug)
.select(bug, report.count(), report.installationId.countDistinct())
.fetch();
String body = tuples.stream().map(tuple -> {
Bug bug = tuple.get(QBug.bug);
return i18nProvider.getTranslation(Messages.WEEKLY_MAIL_BUG_TEMPLATE, Locale.ENGLISH, configuration.getUrl(ReportTab.class, bug.getId()), bug.getTitle(), tuple.get(report.count()), tuple.get(report.installationId.countDistinct()));
}).collect(Collectors.joining("\n"));
if (body.isEmpty()) body = i18nProvider.getTranslation(Messages.WEEKLY_MAIL_NO_REPORTS, Locale.ENGLISH);
try {
MimeMessage template = mailSender.createMimeMessage();
template.setContent(body, "text/html");
template.setSubject(i18nProvider.getTranslation(Messages.WEEKLY_MAIL_SUBJECT, Locale.ENGLISH, entry.getKey().getName()));
for (MailSettings s : entry.getValue()) {
User user = s.getUser();
if (user.getMail() != null) {
MimeMessage message = new MimeMessage(template);
message.setRecipients(Message.RecipientType.TO, user.getMail());
mailSender.send(message);
}
}
} catch (MessagingException e) {
e.printStackTrace();
}
}
}

View file

@ -727,3 +727,73 @@ databaseChangeLog:
columnName: solved
- dropTable:
tableName: proguard_mapping
- changeSet:
id: 2019-02-19-mail
author: lukas
changes:
- createTable:
tableName: mail_settings
columns:
- column:
name: app_id
type: INT
constraints:
nullable: false
referencedTableName: app
referencedColumnNames: id
foreignKeyName: FK_mail_app
deferrable: false
initiallyDeffered: false
- column:
name: username
type: VARCHAR(255)
constraints:
nullable: false
referencedTableName: user
referencedColumnNames: username
foreignKeyName: FK_mail_user
deferrable: false
initiallyDeffered: false
- column:
name: new_bug
type: BOOLEAN
constraints:
nullable: false
deferrable: false
initiallyDeffered: false
- column:
name: regression
type: BOOLEAN
constraints:
nullable: false
deferrable: false
initiallyDeffered: false
- column:
name: spike
type: BOOLEAN
constraints:
nullable: false
deferrable: false
initiallyDeffered: false
- column:
name: summary
type: BOOLEAN
constraints:
nullable: false
deferrable: false
initiallyDeffered: false
- addPrimaryKey:
tableName: mail_settings
columnNames: app_id, username
constraintName: PK_mail
- addColumn:
tableName: user
columns:
- column:
name: mail
type: VARCHAR(255)
constraints:
nullable: true
deferrable: false
initiallyDeffered: false

View file

@ -133,4 +133,7 @@ api=API-Zugriff
about=Info
overview=Übersicht
versions=Versionen
newVersion=Neue Version
newVersion=Neue Version
weeklyMailSubject=Wöchentliche Zusammenfassung für %s
weeklyMailBugTemplate=- <a href='%s'>%s</a>: %d Berichte von %d Nutzern
weeklyMailNoReports=Glückwunsch, es wurden keine Bugs gemeldet!

View file

@ -133,4 +133,7 @@ api=API Access
about=About
overview=Overview
versions=Versions
newVersion=New Version
newVersion=New Version
weeklyMailBugTemplate=- <a href='%s'>%s</a>: %d reports by %d users
weeklyMailSubject=Weekly summary for %s
weeklyMailNoReports=Congratulations, there were no bugs reported!