From 2404d017b8dbc7e6dbdd3f619474793f480673be Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Fri, 24 Aug 2018 02:50:07 +0200 Subject: [PATCH] method level security for dataservice --- .../java/com/faendir/acra/model/User.java | 3 +- .../faendir/acra/rest/RestApiInterface.java | 40 +++++++++++++ .../acra/rest/RestReportInterface.java | 6 +- .../acra/security/SecurityConfiguration.java | 23 +++++--- .../com/faendir/acra/service/DataService.java | 58 ++++++++++++++++--- .../acra/ui/view/app/tabs/StatisticsTab.java | 3 +- .../ui/view/base/statistics/Property.java | 22 ++++--- .../ui/view/base/statistics/Statistics.java | 16 ++--- 8 files changed, 131 insertions(+), 40 deletions(-) create mode 100644 src/main/java/com/faendir/acra/rest/RestApiInterface.java diff --git a/src/main/java/com/faendir/acra/model/User.java b/src/main/java/com/faendir/acra/model/User.java index 09fe46f..0d4393d 100644 --- a/src/main/java/com/faendir/acra/model/User.java +++ b/src/main/java/com/faendir/acra/model/User.java @@ -111,7 +111,8 @@ public class User implements UserDetails { public enum Role implements GrantedAuthority { ADMIN, USER, - REPORTER; + REPORTER, + API; @Override public String getAuthority() { diff --git a/src/main/java/com/faendir/acra/rest/RestApiInterface.java b/src/main/java/com/faendir/acra/rest/RestApiInterface.java new file mode 100644 index 0000000..a7a12c1 --- /dev/null +++ b/src/main/java/com/faendir/acra/rest/RestApiInterface.java @@ -0,0 +1,40 @@ +/* + * (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r) + * + * 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 com.faendir.acra.rest; + +import com.faendir.acra.service.DataService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author lukas + * @since 23.08.18 + */ +@RestController +@RequestMapping(RestApiInterface.API_PATH) +@PreAuthorize("hasRole(T(com.faendir.acra.model.User$Role).API)") +public class RestApiInterface { + public static final String API_PATH = "api"; + @NonNull private final DataService dataService; + + @Autowired + public RestApiInterface(@NonNull DataService dataService) { + this.dataService = dataService; + } +} diff --git a/src/main/java/com/faendir/acra/rest/RestReportInterface.java b/src/main/java/com/faendir/acra/rest/RestReportInterface.java index c0cdc57..3e37500 100644 --- a/src/main/java/com/faendir/acra/rest/RestReportInterface.java +++ b/src/main/java/com/faendir/acra/rest/RestReportInterface.java @@ -96,10 +96,10 @@ public class RestReportInterface { if (!app.isPresent()) { return ResponseEntity.notFound().build(); } - BooleanExpression where = report.stacktrace.bug.app.eq(app.get()); + BooleanExpression where = null; String name = ""; if (mail != null && !mail.isEmpty()) { - where = where.and(report.userEmail.eq(mail)); + where = report.userEmail.eq(mail).and(where); name += "_" + mail; } if (id != null && !id.isEmpty()) { @@ -111,6 +111,6 @@ public class RestReportInterface { } HttpHeaders headers = new HttpHeaders(); headers.setContentDispositionFormData("attachment", "reports" + name + ".json"); - return ResponseEntity.ok().headers(headers).body(dataService.getFromReports(where, report.content, report.id).stream().collect(Collectors.joining(", ", "[", "]"))); + return ResponseEntity.ok().headers(headers).body(dataService.getFromReports(app.get(), where, report.content, report.id).stream().collect(Collectors.joining(", ", "[", "]"))); } } diff --git a/src/main/java/com/faendir/acra/security/SecurityConfiguration.java b/src/main/java/com/faendir/acra/security/SecurityConfiguration.java index af67e39..f570320 100644 --- a/src/main/java/com/faendir/acra/security/SecurityConfiguration.java +++ b/src/main/java/com/faendir/acra/security/SecurityConfiguration.java @@ -16,6 +16,7 @@ package com.faendir.acra.security; import com.faendir.acra.model.User; +import com.faendir.acra.rest.RestApiInterface; import com.faendir.acra.rest.RestReportInterface; import com.faendir.acra.service.UserService; import org.apache.commons.text.RandomStringGenerator; @@ -54,7 +55,6 @@ import java.util.stream.Stream; @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { - @NonNull private final UserService userService; @Autowired @@ -116,17 +116,22 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(@NonNull HttpSecurity http) throws Exception { - // @formatter:off - http - .csrf().disable() - .headers().disable() - .anonymous().disable() - .exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint()) + http.csrf() + .disable() + .headers() + .disable() + .anonymous() + .disable() + .exceptionHandling() + .authenticationEntryPoint(new Http403ForbiddenEntryPoint()) .and() .sessionManagement() .and() - .antMatcher("/"+RestReportInterface.REPORT_PATH).httpBasic(); - // @formatter:on + .antMatcher("/" + RestReportInterface.REPORT_PATH) + .httpBasic() + .and() + .antMatcher("/" + RestApiInterface.API_PATH + "/**") + .httpBasic(); } @NonNull diff --git a/src/main/java/com/faendir/acra/service/DataService.java b/src/main/java/com/faendir/acra/service/DataService.java index e2a233b..71273ee 100644 --- a/src/main/java/com/faendir/acra/service/DataService.java +++ b/src/main/java/com/faendir/acra/service/DataService.java @@ -56,6 +56,9 @@ import org.hibernate.Session; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.access.prepost.PostFilter; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -118,6 +121,7 @@ public class DataService implements Serializable { } @NonNull + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).VIEW)") public QueryDslDataProvider getBugProvider(@NonNull App app, BooleanSupplier onlyNonSolvedProvider) { Supplier whereSupplier = () -> onlyNonSolvedProvider.getAsBoolean() ? bug.app.eq(app).and(bug.solved.eq(false)) : bug.app.eq(app); return new QueryDslDataProvider<>(() -> Queries.selectVBug(entityManager).where(whereSupplier.get()), @@ -125,6 +129,7 @@ public class DataService implements Serializable { } @NonNull + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#bug.app, T(com.faendir.acra.model.Permission$Level).VIEW)") public QueryDslDataProvider getReportProvider(@NonNull Bug bug) { return new QueryDslDataProvider<>(new JPAQuery<>(entityManager).from(report) .join(report.stacktrace, stacktrace1) @@ -134,6 +139,7 @@ public class DataService implements Serializable { } @NonNull + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).VIEW)") public QueryDslDataProvider getReportProvider(@NonNull App app) { return new QueryDslDataProvider<>(new JPAQuery<>(entityManager).from(report) .join(report.stacktrace, stacktrace1) @@ -144,6 +150,7 @@ public class DataService implements Serializable { } @NonNull + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).VIEW)") public QueryDslDataProvider getMappingProvider(@NonNull App app) { return new QueryDslDataProvider<>(new JPAQuery<>(entityManager).from(proguardMapping).where(proguardMapping.app.eq(app)).select(proguardMapping)); } @@ -166,6 +173,7 @@ public class DataService implements Serializable { */ @Transactional @NonNull + @PreAuthorize("hasRole(T(com.faendir.acra.model.User$Role).ADMIN)") public PlainTextUser createNewApp(@NonNull String name) { PlainTextUser user = userService.createReporterUser(); store(new App(name, user)); @@ -174,6 +182,7 @@ public class DataService implements Serializable { @Transactional @NonNull + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).ADMIN)") public PlainTextUser recreateReporterUser(@NonNull App app) { PlainTextUser user = userService.createReporterUser(); app.setReporter(user); @@ -181,23 +190,27 @@ public class DataService implements Serializable { return user; } + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#bugs[0].app, T(com.faendir.acra.model.Permission$Level).EDIT)") public void mergeBugs(@NonNull @Size(min = 2) Collection bugs, @NonNull String title) { bugMerger.mergeBugs(bugs, title); } @Transactional + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#bug.app, T(com.faendir.acra.model.Permission$Level).EDIT)") public void unmergeBug(@NonNull Bug bug) { getStacktraces(bug).forEach(stacktrace -> stacktrace.setBug(new Bug(bug.getApp(), stacktrace.getStacktrace()))); delete(bug); } @Transactional + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#bug.app, T(com.faendir.acra.model.Permission$Level).EDIT)") public void setBugSolved(@NonNull Bug bug, boolean solved) { bug.setSolved(solved); store(bug); } @NonNull + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).VIEW)") public Optional findMapping(@NonNull App app, int versionCode) { return Optional.ofNullable(new JPAQuery<>(entityManager).from(proguardMapping) .where(proguardMapping.app.eq(app).and(proguardMapping.versionCode.eq(versionCode))) @@ -206,6 +219,7 @@ public class DataService implements Serializable { } @NonNull + @PostAuthorize("!returnObject.isPresent() || T(com.faendir.acra.security.SecurityUtils).hasPermission(returnObject.get().stacktrace.bug.app, T(com.faendir.acra.model.Permission$Level).VIEW)") public Optional findReport(@NonNull String id) { return Optional.ofNullable(new JPAQuery<>(entityManager).from(report) .join(report.stacktrace, stacktrace1) @@ -220,6 +234,7 @@ public class DataService implements Serializable { } @NonNull + @PostAuthorize("!returnObject.isPresent() || T(com.faendir.acra.security.SecurityUtils).hasPermission(returnObject.get().app, T(com.faendir.acra.model.Permission$Level).VIEW)") public Optional findBug(@NonNull String encodedId) { try { return Optional.ofNullable(new JPAQuery<>(entityManager).from(bug).join(bug.app).fetchJoin().where(bug.id.eq(Integer.parseInt(encodedId))).select(bug).fetchOne()); @@ -229,6 +244,7 @@ public class DataService implements Serializable { } @NonNull + @PostAuthorize("!returnObject.isPresent() || T(com.faendir.acra.security.SecurityUtils).hasPermission(returnObject.get(), T(com.faendir.acra.model.Permission$Level).VIEW)") public Optional findApp(@NonNull String encodedId) { try { return Optional.ofNullable(new JPAQuery<>(entityManager).from(app).where(app.id.eq(Integer.parseInt(encodedId))).select(app).fetchOne()); @@ -238,22 +254,26 @@ public class DataService implements Serializable { } @NonNull + @PostFilter("T(com.faendir.acra.security.SecurityUtils).hasPermission(filterObject, T(com.faendir.acra.model.Permission$Level).VIEW)") public List findAllApps() { return new JPAQuery<>(entityManager).from(app).select(app).fetch(); } @NonNull + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#report.stacktrace.bug.app, T(com.faendir.acra.model.Permission$Level).VIEW)") public List findAttachments(@NonNull Report report) { return new JPAQuery<>(entityManager).from(attachment).where(attachment.report.eq(report)).select(attachment).fetch(); } - public Optional findStacktrace(@NonNull String stacktrace, int versionCode) { + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).VIEW)") + public Optional findStacktrace(@NonNull App app, @NonNull String stacktrace, int versionCode) { return Optional.ofNullable(new JPAQuery<>(entityManager).from(stacktrace1) - .where(stacktrace1.stacktrace.eq(stacktrace).and(stacktrace1.version.code.eq(versionCode))) + .where(stacktrace1.stacktrace.eq(stacktrace).and(stacktrace1.version.code.eq(versionCode)).and(stacktrace1.bug.app.eq(app))) .select(stacktrace1) .fetchOne()); } + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).VIEW)") private Optional findBug(@NonNull App app, @NonNull String stacktrace) { return Optional.ofNullable(new JPAQuery<>(entityManager).from(stacktrace1) .join(stacktrace1.bug, bug) @@ -263,11 +283,13 @@ public class DataService implements Serializable { } @Transactional + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).EDIT)") public void changeConfiguration(@NonNull App app, @NonNull App.Configuration configuration) { bugMerger.changeConfiguration(app, configuration); } @Transactional + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).EDIT)") public void deleteReportsOlderThanDays(@NonNull App app, @NonNull int days) { new JPADeleteClause(entityManager, report).where(report.stacktrace.bug.app.eq(app).and(report.date.before(ZonedDateTime.now().minus(days, ChronoUnit.DAYS)))); entityManager.flush(); @@ -275,6 +297,7 @@ public class DataService implements Serializable { } @Transactional + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).EDIT)") public void deleteReportsBeforeVersion(@NonNull App app, int versionCode) { new JPADeleteClause(entityManager, report).where(report.stacktrace.bug.app.eq(app).and(report.stacktrace.version.code.lt(versionCode))); entityManager.flush(); @@ -282,13 +305,14 @@ public class DataService implements Serializable { } @Transactional + @PreAuthorize("hasRole(T(com.faendir.acra.model.User$Role).REPORTER)") public void createNewReport(@NonNull String reporterUserName, @NonNull String content, @NonNull List attachments) { App app = new JPAQuery<>(entityManager).from(QApp.app).where(QApp.app.reporter.username.eq(reporterUserName)).select(QApp.app).fetchOne(); if (app != null) { JSONObject jsonObject = new JSONObject(content); String trace = jsonObject.optString(ReportField.STACK_TRACE.name()); Version version = getVersion(jsonObject); - Stacktrace stacktrace = findStacktrace(trace, version.getCode()).orElseGet(() -> new Stacktrace(findBug(app, trace).orElseGet(() -> new Bug(app, trace)), + Stacktrace stacktrace = findStacktrace(app, trace, version.getCode()).orElseGet(() -> new Stacktrace(findBug(app, trace).orElseGet(() -> new Bug(app, trace)), trace, version)); Report report = store(new Report(stacktrace, content)); @@ -328,32 +352,48 @@ public class DataService implements Serializable { return new Version(versionCode, versionName); } - public Map countReports(@NonNull Predicate where, @NonNull Expression select) { - List result = ((JPAQuery) new JPAQuery<>(entityManager)).from(report).where(where).groupBy(select).select(select, report.id.count()).fetch(); + @NonNull + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).VIEW)") + public Map countReports(@NonNull App app, @NonNull Predicate where, @NonNull Expression select) { + List result = ((JPAQuery) new JPAQuery<>(entityManager)).from(report) + .where(report.stacktrace.bug.app.eq(app).and(where)) + .groupBy(select) + .select(select, report.id.count()) + .fetch(); return result.stream().collect(Collectors.toMap(tuple -> tuple.get(select), tuple -> Optional.ofNullable(tuple.get(report.id.count())).orElse(0L))); } @NonNull - public List getFromReports(@NonNull Predicate where, @NonNull ComparableExpressionBase select) { - return getFromReports(where, select, select); + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).VIEW)") + public List getFromReports(@NonNull App app, @NonNull Predicate where, @NonNull ComparableExpressionBase select) { + return getFromReports(app, where, select, select); } @NonNull - public List getFromReports(@NonNull Predicate where, @NonNull Expression select, ComparableExpressionBase order) { - return ((JPAQuery) new JPAQuery<>(entityManager)).from(report).where(where).select(select).distinct().orderBy(order.asc()).fetch(); + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).VIEW)") + public List getFromReports(@NonNull App app, @NonNull Predicate where, @NonNull Expression select, ComparableExpressionBase order) { + return ((JPAQuery) new JPAQuery<>(entityManager)).from(report) + .where(report.stacktrace.bug.app.eq(app).and(where)) + .select(select) + .distinct() + .orderBy(order.asc()) + .fetch(); } @NonNull + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#bug.app, T(com.faendir.acra.model.Permission$Level).VIEW)") public List getStacktraces(@NonNull Bug bug) { return new JPAQuery<>(entityManager).from(stacktrace1).where(stacktrace1.bug.eq(bug)).select(stacktrace1).fetch(); } @NonNull + @PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).VIEW)") public Optional getMaximumMappingVersion(@NonNull App app) { return Optional.ofNullable(new JPAQuery<>(entityManager).from(proguardMapping).where(proguardMapping.app.eq(app)).select(proguardMapping.versionCode.max()).fetchOne()); } @Transactional + @PreAuthorize("hasRole(T(com.faendir.acra.model.User$Role).ADMIN)") public ImportResult importFromAcraStorage(String host, int port, boolean ssl, String database) { HttpClient httpClient = new StdHttpClient.Builder().host(host).port(port).enableSSL(ssl).build(); CouchDbConnector db = new StdCouchDbConnector(database, new StdCouchDbInstance(httpClient)); diff --git a/src/main/java/com/faendir/acra/ui/view/app/tabs/StatisticsTab.java b/src/main/java/com/faendir/acra/ui/view/app/tabs/StatisticsTab.java index 1c694e4..cdc9d99 100644 --- a/src/main/java/com/faendir/acra/ui/view/app/tabs/StatisticsTab.java +++ b/src/main/java/com/faendir/acra/ui/view/app/tabs/StatisticsTab.java @@ -18,7 +18,6 @@ package com.faendir.acra.ui.view.app.tabs; import com.faendir.acra.i18n.Messages; import com.faendir.acra.model.App; -import com.faendir.acra.model.QReport; import com.faendir.acra.service.DataService; import com.faendir.acra.ui.navigation.NavigationManager; import com.faendir.acra.ui.view.base.statistics.Statistics; @@ -49,7 +48,7 @@ public class StatisticsTab implements AppTab { @Override public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) { - Panel root = new Panel(new Statistics(QReport.report.stacktrace.bug.app.eq(app), dataService, i18n)); + Panel root = new Panel(new Statistics(app, null, dataService, i18n)); root.setSizeFull(); root.addStyleNames(AcraTheme.NO_BACKGROUND, AcraTheme.NO_BORDER); return root; diff --git a/src/main/java/com/faendir/acra/ui/view/base/statistics/Property.java b/src/main/java/com/faendir/acra/ui/view/base/statistics/Property.java index 8dbe22c..8cee0e1 100644 --- a/src/main/java/com/faendir/acra/ui/view/base/statistics/Property.java +++ b/src/main/java/com/faendir/acra/ui/view/base/statistics/Property.java @@ -16,6 +16,7 @@ package com.faendir.acra.ui.view.base.statistics; import com.faendir.acra.i18n.I18nCheckBox; +import com.faendir.acra.model.App; import com.faendir.acra.service.DataService; import com.querydsl.core.types.Expression; import com.querydsl.core.types.dsl.BooleanExpression; @@ -28,6 +29,7 @@ import com.vaadin.ui.CheckBox; import com.vaadin.ui.ComboBox; import com.vaadin.ui.Component; import com.vaadin.ui.ComponentContainer; +import org.springframework.lang.Nullable; import org.vaadin.risto.stepper.IntStepper; import org.vaadin.spring.i18n.I18N; @@ -41,6 +43,7 @@ import java.util.function.Function; * @since 01.06.18 */ class Property, T> { + private final App app; private final DataService dataService; private final CheckBox checkBox; private final C filterComponent; @@ -48,7 +51,8 @@ class Property, T> { private final Chart chart; private final Expression select; - private Property(C filterComponent, Function filter, Chart chart, DataService dataService, Expression select, I18N i18n, String filterTextId) { + private Property(App app, C filterComponent, Function filter, Chart chart, DataService dataService, Expression select, I18N i18n, String filterTextId) { + this.app = app; this.dataService = dataService; this.checkBox = new I18nCheckBox(i18n, filterTextId); this.filterComponent = filterComponent; @@ -67,15 +71,15 @@ class Property, T> { chartLayout.addComponent(chart); } - BooleanExpression applyFilter(BooleanExpression expression) { + BooleanExpression applyFilter(@Nullable BooleanExpression expression) { if (checkBox.getValue() && filterComponent.getValue() != null) { - return expression.and(filter.apply(filterComponent.getValue())); + return filter.apply(filterComponent.getValue()).and(expression); } return expression; } void update(BooleanExpression expression) { - chart.setContent(dataService.countReports(expression, select)); + chart.setContent(dataService.countReports(app, expression, select)); } static class Factory { @@ -87,17 +91,17 @@ class Property, T> { this.expression = expression; } - Property createStringProperty(ComparableExpressionBase stringExpression, I18N i18n, String filterTextId, String chartTitleId) { - ComboBox comboBox = new ComboBox<>(null, dataService.getFromReports(expression, stringExpression)); + Property createStringProperty(App app, ComparableExpressionBase stringExpression, I18N i18n, String filterTextId, String chartTitleId) { + ComboBox comboBox = new ComboBox<>(null, dataService.getFromReports(app, expression, stringExpression)); comboBox.setEmptySelectionAllowed(false); - return new Property<>(comboBox, stringExpression::eq, new PieChart(i18n, chartTitleId), dataService, stringExpression, i18n, filterTextId); + return new Property<>(app, comboBox, stringExpression::eq, new PieChart(i18n, chartTitleId), dataService, stringExpression, i18n, filterTextId); } - Property createAgeProperty(DateTimePath dateTimeExpression, I18N i18n, String filterTextId, String chartTitleId) { + Property createAgeProperty(App app, DateTimePath dateTimeExpression, I18N i18n, String filterTextId, String chartTitleId) { IntStepper stepper = new IntStepper(); stepper.setValue(30); stepper.setMinValue(1); - return new Property<>(stepper, + return new Property<>(app, stepper, days -> dateTimeExpression.after(ZonedDateTime.now().minus(days, ChronoUnit.DAYS)), new TimeChart(i18n, chartTitleId), dataService, diff --git a/src/main/java/com/faendir/acra/ui/view/base/statistics/Statistics.java b/src/main/java/com/faendir/acra/ui/view/base/statistics/Statistics.java index e949ef9..ac4ac24 100644 --- a/src/main/java/com/faendir/acra/ui/view/base/statistics/Statistics.java +++ b/src/main/java/com/faendir/acra/ui/view/base/statistics/Statistics.java @@ -19,6 +19,7 @@ package com.faendir.acra.ui.view.base.statistics; import com.faendir.acra.i18n.I18nButton; import com.faendir.acra.i18n.I18nPanel; import com.faendir.acra.i18n.Messages; +import com.faendir.acra.model.App; import com.faendir.acra.model.QReport; import com.faendir.acra.service.DataService; import com.faendir.acra.ui.view.base.layout.FlexLayout; @@ -29,6 +30,7 @@ import com.vaadin.ui.Composite; import com.vaadin.ui.GridLayout; import com.vaadin.ui.Panel; import com.vaadin.ui.themes.AcraTheme; +import org.springframework.lang.Nullable; import org.vaadin.risto.stepper.IntStepper; import org.vaadin.spring.i18n.I18N; @@ -44,10 +46,10 @@ public class Statistics extends Composite { static final Color BLUE = new Color(0x197de1); //vaadin blue static final Color FOREGROUND_DARK = new Color(0xcacecf); static final Color FOREGROUND_LIGHT = new Color(0x464646); - private final BooleanExpression baseExpression; + @Nullable private final BooleanExpression baseExpression; private final List> properties; - public Statistics(BooleanExpression baseExpression, DataService dataService, I18N i18n) { + public Statistics(App app, @Nullable BooleanExpression baseExpression, DataService dataService, I18N i18n) { this.baseExpression = baseExpression; properties = new ArrayList<>(); GridLayout filterLayout = new GridLayout(2, 1); @@ -61,11 +63,11 @@ public class Statistics extends Composite { dayStepper.setValue(30); dayStepper.setMinValue(1); Property.Factory factory = new Property.Factory(dataService, baseExpression); - properties.add(factory.createAgeProperty(QReport.report.date, i18n, Messages.LAST_X_DAYS, Messages.REPORTS_OVER_TIME)); - properties.add(factory.createStringProperty(QReport.report.androidVersion, i18n, Messages.ANDROID_VERSION, Messages.REPORTS_PER_ANDROID_VERSION)); - properties.add(factory.createStringProperty(QReport.report.stacktrace.version.name, i18n, Messages.APP_VERSION, Messages.REPORTS_PER_APP_VERSION)); - properties.add(factory.createStringProperty(QReport.report.phoneModel, i18n, Messages.PHONE_MODEL, Messages.REPORTS_PER_PHONE_MODEL)); - properties.add(factory.createStringProperty(QReport.report.brand, i18n, Messages.PHONE_BRAND, Messages.REPORTS_PER_BRAND)); + properties.add(factory.createAgeProperty(app, QReport.report.date, i18n, Messages.LAST_X_DAYS, Messages.REPORTS_OVER_TIME)); + properties.add(factory.createStringProperty(app, QReport.report.androidVersion, i18n, Messages.ANDROID_VERSION, Messages.REPORTS_PER_ANDROID_VERSION)); + properties.add(factory.createStringProperty(app, QReport.report.stacktrace.version.name, i18n, Messages.APP_VERSION, Messages.REPORTS_PER_APP_VERSION)); + properties.add(factory.createStringProperty(app, QReport.report.phoneModel, i18n, Messages.PHONE_MODEL, Messages.REPORTS_PER_PHONE_MODEL)); + properties.add(factory.createStringProperty(app, QReport.report.brand, i18n, Messages.PHONE_BRAND, Messages.REPORTS_PER_BRAND)); Panel filterPanel = new I18nPanel(filterLayout, i18n, Messages.FILTER); filterPanel.addStyleName(AcraTheme.NO_BACKGROUND);