Add statistics
This commit is contained in:
parent
2475784d52
commit
1445b987a0
12 changed files with 205 additions and 41 deletions
|
@ -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')
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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" />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue