implement weekly report sending
This commit is contained in:
parent
9c3ac2c6f2
commit
b9f74d1f0f
9 changed files with 155 additions and 62 deletions
|
@ -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'
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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!
|
|
@ -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!
|
Loading…
Reference in a new issue