method level security for dataservice

This commit is contained in:
f43nd1r 2018-08-24 02:50:07 +02:00
parent 864874e5b2
commit 2404d017b8
8 changed files with 131 additions and 40 deletions

View file

@ -111,7 +111,8 @@ public class User implements UserDetails {
public enum Role implements GrantedAuthority {
ADMIN,
USER,
REPORTER;
REPORTER,
API;
@Override
public String getAuthority() {

View file

@ -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;
}
}

View file

@ -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(", ", "[", "]")));
}
}

View file

@ -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

View file

@ -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<VBug> getBugProvider(@NonNull App app, BooleanSupplier onlyNonSolvedProvider) {
Supplier<BooleanExpression> 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<Report> 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<Report> 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<ProguardMapping> 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<Bug> 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<ProguardMapping> 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<Report> 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<Bug> 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<App> 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<App> 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<Attachment> findAttachments(@NonNull Report report) {
return new JPAQuery<>(entityManager).from(attachment).where(attachment.report.eq(report)).select(attachment).fetch();
}
public Optional<Stacktrace> 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<Stacktrace> 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<Bug> 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<MultipartFile> 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 <T> Map<T, Long> countReports(@NonNull Predicate where, @NonNull Expression<T> select) {
List<Tuple> 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 <T> Map<T, Long> countReports(@NonNull App app, @NonNull Predicate where, @NonNull Expression<T> select) {
List<Tuple> 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 <T extends Comparable> List<T> getFromReports(@NonNull Predicate where, @NonNull ComparableExpressionBase<T> select) {
return getFromReports(where, select, select);
@PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).VIEW)")
public <T extends Comparable> List<T> getFromReports(@NonNull App app, @NonNull Predicate where, @NonNull ComparableExpressionBase<T> select) {
return getFromReports(app, where, select, select);
}
@NonNull
public <T> List<T> getFromReports(@NonNull Predicate where, @NonNull Expression<T> 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 <T> List<T> getFromReports(@NonNull App app, @NonNull Predicate where, @NonNull Expression<T> 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<Stacktrace> 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<Integer> 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));

View file

@ -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;

View file

@ -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<F, C extends Component & HasValue<F>, T> {
private final App app;
private final DataService dataService;
private final CheckBox checkBox;
private final C filterComponent;
@ -48,7 +51,8 @@ class Property<F, C extends Component & HasValue<F>, T> {
private final Chart<T> chart;
private final Expression<T> select;
private Property(C filterComponent, Function<F, BooleanExpression> filter, Chart<T> chart, DataService dataService, Expression<T> select, I18N i18n, String filterTextId) {
private Property(App app, C filterComponent, Function<F, BooleanExpression> filter, Chart<T> chart, DataService dataService, Expression<T> 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<F, C extends Component & HasValue<F>, 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<F, C extends Component & HasValue<F>, T> {
this.expression = expression;
}
Property<?, ?, ?> createStringProperty(ComparableExpressionBase<String> stringExpression, I18N i18n, String filterTextId, String chartTitleId) {
ComboBox<String> comboBox = new ComboBox<>(null, dataService.getFromReports(expression, stringExpression));
Property<?, ?, ?> createStringProperty(App app, ComparableExpressionBase<String> stringExpression, I18N i18n, String filterTextId, String chartTitleId) {
ComboBox<String> 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<ZonedDateTime> dateTimeExpression, I18N i18n, String filterTextId, String chartTitleId) {
Property<?, ?, ?> createAgeProperty(App app, DateTimePath<ZonedDateTime> 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,

View file

@ -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<Property<?, ?, ?>> 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);