Add statistics

This commit is contained in:
F43nd1r 2017-05-26 19:14:08 +02:00
parent 2475784d52
commit 1445b987a0
12 changed files with 205 additions and 41 deletions

View file

@ -26,16 +26,22 @@ ext {
}
dependencies {
//core
compile 'org.springframework.boot:spring-boot-starter-data-mongodb'
compile 'org.springframework.boot:spring-boot-starter-security'
compile 'com.vaadin:vaadin-spring-boot-starter'
//addons
compile 'org.vaadin.addons:popupbutton:3.0.0'
compile 'org.vaadin.addon:jfreechartwrapper:4.0.0'
compile 'org.vaadin.addons:stepper:2.4.0'
//utility
compile 'org.codeartisans:org.json:20161124'
compile 'org.apache.commons:commons-collections4:4.1'
compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final'
compile 'commons-fileupload:commons-fileupload:1.3.2'
compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final'
compile 'net.sf.proguard:proguard-retrace:5.3.3'
compile 'org.vaadin.addons:popupbutton:3.0.0'
compile 'org.jfree:jfreechart:1.0.19'
//local
compileOnly project(':annotation')
apt project(':annotationprocessor')
}

View file

@ -9,10 +9,10 @@ import com.faendir.acra.ui.view.base.ReportList;
import com.faendir.acra.ui.view.tabs.BugTab;
import com.faendir.acra.ui.view.tabs.DeObfuscationTab;
import com.faendir.acra.ui.view.tabs.PropertiesTab;
import com.faendir.acra.ui.view.tabs.StatisticsTab;
import com.faendir.acra.util.Style;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.ui.Label;
import com.vaadin.ui.TabSheet;
import com.vaadin.ui.VerticalLayout;
import org.springframework.beans.factory.annotation.Autowired;
@ -47,12 +47,9 @@ public class AppView extends NamedView {
public void enter(ViewChangeListener.ViewChangeEvent event) {
String[] parameters = event.getParameters().split("/");
App app = dataManager.getApp(parameters[0]);
VerticalLayout statistics = new VerticalLayout(new Label("Coming soon"));
statistics.setCaption("Statistics");
statistics.setSizeFull();
TabSheet tabSheet = new TabSheet(new BugTab(app.getId(), getNavigationManager(), dataManager),
new ReportList(app.getId(), getNavigationManager(), dataManager),
statistics, new DeObfuscationTab(app.getId(), dataManager));
new StatisticsTab(app.getId(), dataManager), new DeObfuscationTab(app.getId(), dataManager));
if(SecurityUtils.hasPermission(app.getId(), Permission.Level.ADMIN)){
tabSheet.addComponent(new PropertiesTab(app, dataManager, getNavigationManager()));
}

View file

@ -64,7 +64,7 @@ public class Overview extends NamedView {
grid.setSizeFull();
grid.setSelectionMode(Grid.SelectionMode.NONE);
grid.addColumn(App::getName, "Name");
grid.addColumn(app -> String.valueOf(dataManager.getReports(app.getId()).size()), "Reports");
grid.addColumn(app -> dataManager.getReports(app.getId()).size(), "Reports");
VerticalLayout layout = new VerticalLayout(grid);
if(SecurityUtils.hasRole(UserManager.ROLE_ADMIN)){
Button add = new Button("New App", e -> addApp());

View file

@ -2,6 +2,8 @@ package com.faendir.acra.ui.view.base;
import com.vaadin.data.ValueProvider;
import com.vaadin.ui.Grid;
import com.vaadin.ui.renderers.AbstractRenderer;
import com.vaadin.ui.renderers.TextRenderer;
import java.util.Collection;
@ -18,10 +20,11 @@ public class MyGrid<T> extends Grid<T> {
super(caption, items);
}
public Grid.Column<T, String> addColumn(ValueProvider<T, String> valueProvider, String caption) {
Grid.Column<T, String> column = addColumn(valueProvider);
column.setId(caption);
column.setCaption(caption);
return column;
public <R> Grid.Column<T, R> addColumn(ValueProvider<T, R> valueProvider, String caption) {
return addColumn(valueProvider, new TextRenderer(), caption);
}
public <R> Grid.Column<T, R> addColumn(ValueProvider<T, R> valueProvider, AbstractRenderer<? super T, ? super R> renderer, String caption) {
return addColumn(valueProvider, renderer).setId(caption).setCaption(caption);
}
}

View file

@ -6,7 +6,8 @@ import com.faendir.acra.mongod.model.Report;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.NavigationManager;
import com.faendir.acra.ui.view.ReportView;
import com.faendir.acra.util.StringUtils;
import com.faendir.acra.util.TimeSpanRenderer;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.ui.renderers.ButtonRenderer;
/**
@ -23,12 +24,12 @@ public class ReportList extends MyGrid<Report> implements DataManager.ReportChan
this.dataManager = dataManager;
setSizeFull();
setSelectionMode(SelectionMode.NONE);
addColumn(report -> StringUtils.distanceFromNowAsString(report.getDate()), "Date");
addColumn(report -> String.valueOf(report.getVersionCode()), "App Version");
sort(addColumn(Report::getDate, new TimeSpanRenderer(), "Date"), SortDirection.DESCENDING);
addColumn(Report::getVersionCode, "App Version");
addColumn(Report::getAndroidVersion, "Android Version");
addColumn(Report::getPhoneModel, "Device");
addColumn(report -> report.getStacktrace().split("\n", 2)[0], "Stacktrace").setExpandRatio(1);
if(SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
addColumn(report -> "Delete", new ButtonRenderer<>(e -> dataManager.remove(e.getItem())));
}
addItemClickListener(e -> navigationManager.navigateTo(ReportView.class, e.getItem().getId()));

View file

@ -1,24 +1,23 @@
package com.faendir.acra.ui.view.tabs;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.model.Bug;
import com.faendir.acra.mongod.data.ReportUtils;
import com.faendir.acra.mongod.model.Bug;
import com.faendir.acra.ui.NavigationManager;
import com.faendir.acra.ui.view.base.MyGrid;
import com.faendir.acra.ui.view.base.ReportList;
import com.faendir.acra.util.StringUtils;
import com.faendir.acra.util.Style;
import com.faendir.acra.util.TimeSpanRenderer;
import com.vaadin.event.selection.SelectionEvent;
import com.vaadin.ui.CustomComponent;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.ui.VerticalLayout;
/**
* @author Lukas
* @since 17.05.2017
*/
public class BugTab extends CustomComponent implements DataManager.ReportChangeListener {
public class BugTab extends VerticalLayout implements DataManager.ReportChangeListener {
public static final String CAPTION = "Bugs";
private final VerticalLayout root;
private final String app;
private final NavigationManager navigationManager;
private final DataManager dataManager;
@ -30,15 +29,13 @@ public class BugTab extends CustomComponent implements DataManager.ReportChangeL
this.dataManager = dataManager;
bugs = new MyGrid<>(null, ReportUtils.getBugs(dataManager.getReports(app)));
bugs.setSizeFull();
bugs.addColumn(bug -> String.valueOf(bug.getReports().size()), "Reports");
bugs.addColumn(bug -> StringUtils.distanceFromNowAsString(bug.getLastDate()), "Latest Report");
bugs.addColumn(bug -> String.valueOf(bug.getVersionCode()), "Version");
bugs.addColumn(bug -> bug.getReports().size(), "Reports");
bugs.sort(bugs.addColumn(Bug::getLastDate, new TimeSpanRenderer(), "Latest Report"), SortDirection.DESCENDING);
bugs.addColumn(Bug::getVersionCode, "Version");
bugs.addColumn(bug -> bug.getTrace().split("\n", 2)[0], "Stacktrace").setExpandRatio(1);
bugs.addSelectionListener(this::handleBugSelection);
root = new VerticalLayout(bugs);
Style.NO_PADDING.apply(root);
root.setSizeFull();
setCompositionRoot(root);
addComponent(bugs);
Style.NO_PADDING.apply(this);
setSizeFull();
setCaption(CAPTION);
addAttachListener(e -> dataManager.addListener(this));
@ -46,10 +43,10 @@ public class BugTab extends CustomComponent implements DataManager.ReportChangeL
}
private void handleBugSelection(SelectionEvent<Bug> e) {
if(root.getComponentCount() == 2){
root.removeComponent(root.getComponent(1));
if (getComponentCount() == 2) {
removeComponent(getComponent(1));
}
e.getFirstSelectedItem().ifPresent(bug -> root.addComponent(new ReportList(app, navigationManager, dataManager)));
e.getFirstSelectedItem().ifPresent(bug -> addComponent(new ReportList(app, navigationManager, dataManager)));
}
@Override

View file

@ -7,7 +7,6 @@ import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.view.base.MyGrid;
import com.faendir.acra.util.Style;
import com.vaadin.ui.Button;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.ProgressBar;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
@ -22,7 +21,7 @@ import java.io.ByteArrayOutputStream;
* @author Lukas
* @since 19.05.2017
*/
public class DeObfuscationTab extends CustomComponent {
public class DeObfuscationTab extends VerticalLayout {
private final String app;
private final DataManager dataManager;
private final MyGrid<ProguardMapping> grid;
@ -34,15 +33,15 @@ public class DeObfuscationTab extends CustomComponent {
grid = new MyGrid<>(null, dataManager.getMappings(app));
this.app = app;
this.dataManager = dataManager;
grid.addColumn(mapping -> String.valueOf(mapping.getVersion()), "Version");
grid.addColumn(ProguardMapping::getVersion, "Version");
grid.setSizeFull();
VerticalLayout layout = new VerticalLayout(grid);
layout.setSizeFull();
Style.NO_PADDING.apply(layout);
setCompositionRoot(layout);
addComponent(grid);
setSizeFull();
Style.NO_PADDING.apply(this);
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
layout.addComponent(new Button("Add File", e -> addFile()));
addComponent(new Button("Add File", e -> addFile()));
}
setExpandRatio(grid, 1);
}

View file

@ -0,0 +1,125 @@
package com.faendir.acra.ui.view.tabs;
import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.model.Report;
import com.faendir.acra.util.Style;
import com.vaadin.ui.Component;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.VerticalLayout;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberTickUnitSource;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
import org.jfree.chart.plot.PieLabelLinkStyle;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.TimeSeriesDataItem;
import org.jfree.util.SortOrder;
import org.vaadin.addon.JFreeChartWrapper;
import org.vaadin.risto.stepper.IntStepper;
import java.awt.*;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Optional;
/**
* @author Lukas
* @since 22.05.2017
*/
public class StatisticsTab extends HorizontalLayout {
private static final String TIME_CHART_ID = "timeChart";
private static final Color BACKGROUND_GRAY = new Color(0xfafafa); //vaadin gray
private static final Color BLUE = new Color(0x197de1); //vaadin blue
private final List<Report> reports;
private final VerticalLayout timeLayout;
public StatisticsTab(String app, DataManager dataManager) {
setCaption("Statistics");
IntStepper numberField = new IntStepper("Days");
numberField.setValue(30);
numberField.setMinValue(5);
numberField.addValueChangeListener(e -> setTimeChart(e.getValue()));
reports = dataManager.getReports(app);
timeLayout = new VerticalLayout(numberField);
Style.NO_PADDING.apply(timeLayout);
addComponent(timeLayout);
setTimeChart(30);
setVersionChart();
setSizeFull();
}
private void setTimeChart(int age) {
for (Component component : this) {
if (TIME_CHART_ID.equals(component.getId())) {
timeLayout.removeComponent(component);
}
}
TimeSeries series = new TimeSeries("Date");
series.setMaximumItemAge(age);
series.add(new Day(new Date()), 0);
Calendar start = Calendar.getInstance();
start.add(Calendar.DAY_OF_MONTH, -age);
series.add(new Day(start.getTime()), 0);
for (Report report : reports) {
Date date = report.getDate();
Day day = new Day(date);
int count = Optional.ofNullable(series.getDataItem(day)).map(TimeSeriesDataItem::getValue).map(Number::intValue).orElse(0);
count++;
series.addOrUpdate(day, count);
}
JFreeChart chart = ChartFactory.createXYBarChart("", "Date", true, "Reports", new TimeSeriesCollection(series), PlotOrientation.VERTICAL, false, false, false);
XYPlot plot = chart.getXYPlot();
plot.getRangeAxis().setStandardTickUnits(new NumberTickUnitSource(true));
plot.setBackgroundPaint(BACKGROUND_GRAY);
chart.setBackgroundPaint(BACKGROUND_GRAY);
plot.setDomainGridlinesVisible(false);
plot.setRangeGridlinePaint(Color.BLACK);
plot.setOutlineVisible(false);
XYBarRenderer barRenderer = (XYBarRenderer) plot.getRenderer();
barRenderer.setBarPainter(new StandardXYBarPainter());
barRenderer.setSeriesPaint(0, BLUE);
barRenderer.setBarAlignmentFactor(0.5);
barRenderer.setMargin(0.2);
JFreeChartWrapper wrapper = new JFreeChartWrapper(chart);
wrapper.setId(TIME_CHART_ID);
timeLayout.addComponent(wrapper);
}
private void setVersionChart() {
DefaultPieDataset dataset = new DefaultPieDataset();
for (Report report : reports) {
String version = report.getAndroidVersion();
int count;
if (dataset.getKeys().contains(version)) {
count = dataset.getValue(version).intValue() + 1;
} else {
count = 1;
}
dataset.insertValue(0, version, count);
}
dataset.sortByKeys(SortOrder.ASCENDING);
JFreeChart chart = ChartFactory.createPieChart("Reports per Android Version", dataset, false, false, false);
PiePlot plot = (PiePlot) chart.getPlot();
plot.setShadowPaint(null);
plot.setBackgroundPaint(BACKGROUND_GRAY);
chart.setBackgroundPaint(BACKGROUND_GRAY);
plot.setOutlineVisible(false);
plot.setLabelBackgroundPaint(BACKGROUND_GRAY);
plot.setLabelOutlinePaint(null);
plot.setLabelShadowPaint(null);
plot.setLabelLinkStyle(PieLabelLinkStyle.QUAD_CURVE);
plot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0} ({2})"));
//noinspection unchecked
((List<String>)dataset.getKeys()).forEach(key->plot.setExplodePercent(key, 0.01));
addComponent(new VerticalLayout(new JFreeChartWrapper(chart)));
}
}

View file

@ -67,6 +67,7 @@ public class UserManagerView extends NamedView {
((user, permissions) -> permissions.forEach(p -> userManager.setPermission(user, p.getApp(), p.getLevel())))));
Button newUser = new Button("New User", e -> newUser());
VerticalLayout layout = new VerticalLayout(userGrid, newUser);
layout.setExpandRatio(userGrid, 1);
layout.setSizeFull();
Style.NO_PADDING.apply(layout);
setCompositionRoot(layout);

View file

@ -0,0 +1,24 @@
package com.faendir.acra.util;
import com.vaadin.ui.renderers.TextRenderer;
import elemental.json.Json;
import elemental.json.JsonValue;
import java.util.Date;
/**
* @author Lukas
* @since 26.05.2017
*/
public class TimeSpanRenderer extends TextRenderer {
public TimeSpanRenderer() {
}
@Override
public JsonValue encode(Object value) {
if (value == null || !(value instanceof Date)) {
return super.encode(null);
}
return Json.create(StringUtils.distanceFromNowAsString((Date) value));
}
}

View file

@ -4,6 +4,7 @@
<module>
<inherits name="com.vaadin.DefaultWidgetSet" />
<inherits name="org.vaadin.hene.popupbutton.widgetset.PopupbuttonWidgetset" />
<inherits name="org.vaadin.risto.stepper.StepperWidgetset" />
<set-configuration-property name="devModeRedirectEnabled" value="true" />
<set-property name="user.agent" value="ie8,ie9,gecko1_8,safari,ie10" />
<source path="client" />

View file

@ -1,7 +1,17 @@
/* This file is automatically managed and will be overwritten from time to time. */
/* Do not manually edit this file. */
/* Provided by stepper-2.4.0.jar */
@import "../../..//VAADIN/addons/stepper/stepper.scss";
/* Provided by stepper-2.4.0.jar */
@import "../../../VAADIN/addons/stepper/stepper.scss";
/* Import and include this mixin into your project theme to include the addon themes */
@mixin addons {
@include stepper;
@include stepper;
}