bug improvements

This commit is contained in:
F43nd1r 2018-03-21 04:29:58 +01:00
parent 9301b3281e
commit 4311a672dc
19 changed files with 345 additions and 92 deletions

View file

@ -1,6 +1,6 @@
buildscript {
ext {
springBootVersion = '2.0.0.M7'
springBootVersion = '2.0.0.RELEASE'
}
repositories {
mavenCentral()
@ -39,7 +39,7 @@ sourceCompatibility = 1.8
targetCompatibility = 1.8
vaadin {
version '8.2.0'
version '8.3.2'
push true
}

View file

@ -4,7 +4,7 @@ import com.faendir.acra.config.AcraConfiguration;
import com.faendir.acra.service.multipart.Rfc1341MultipartResolver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
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;

View file

@ -100,7 +100,7 @@ public class ReportService {
JSONObject jsonObject = new JSONObject(content);
String stacktrace = Utils.generifyStacktrace(jsonObject.optString(ReportField.STACK_TRACE.name()), app.getConfiguration());
Date crashDate = Utils.getDateFromString(jsonObject.optString(ReportField.USER_CRASH_DATE.name()));
Bug bug = bugRepository.findBugByAppAndStacktrace(app, stacktrace)
Bug bug = bugRepository.findBugByAppAndStacktraces(app, stacktrace)
.orElseGet(() -> new Bug(app, stacktrace, jsonObject.optInt(ReportField.APP_VERSION_CODE.name()), crashDate));
bug.setLastReport(crashDate);
Report report = reportRepository.save(new Report(bug, content));

View file

@ -10,6 +10,8 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.lang.NonNull;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
/**
@ -25,7 +27,13 @@ public interface BugRepository extends JpaRepository<Bug, Integer> {
int countAllByAppAndSolvedFalse(@NonNull App app);
Optional<Bug> findBugByAppAndStacktrace(@NonNull App app, @NonNull String stacktrace);
Optional<Bug> findBugByAppAndStacktraces(@NonNull App app, @NonNull String stacktrace);
@Query("select bug from Bug bug join fetch bug.stacktraces where bug in ?1")
List<Bug> loadStacktraces(Collection<Bug> bugs);
@Query("select bug from Bug bug join fetch bug.app join fetch bug.stacktraces where bug.id = ?1")
Optional<Bug> findByIdEager(int id);
@Transactional
@Modifying

View file

@ -7,13 +7,16 @@ import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.lang.NonNull;
import javax.persistence.CascadeType;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author Lukas
@ -24,11 +27,14 @@ public class Bug {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, optional = false, fetch = FetchType.LAZY)
@ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH}, optional = false, fetch = FetchType.LAZY)
@OnDelete(action = OnDeleteAction.CASCADE)
private App app;
private boolean solved;
@Type(type = "text") private String stacktrace;
@ElementCollection(fetch = FetchType.LAZY)
@Type(type = "text")
private List<String> stacktraces;
@Type(type = "text") private String title;
private Date lastReport;
private int versionCode;
@ -38,7 +44,9 @@ public class Bug {
public Bug(@NonNull App app, @NonNull String stacktrace, int versionCode, @NonNull Date lastReport) {
this.app = app;
this.stacktrace = stacktrace;
this.stacktraces = new ArrayList<>();
this.stacktraces.add(stacktrace);
this.title = stacktrace.split("\n", 2)[0];
this.versionCode = versionCode;
this.lastReport = lastReport;
this.solved = false;
@ -66,8 +74,17 @@ public class Bug {
}
@NonNull
public String getStacktrace() {
return stacktrace;
public List<String> getStacktraces() {
return stacktraces != null ? stacktraces : new ArrayList<>();
}
@NonNull
public String getTitle() {
return title;
}
public void setTitle(@NonNull String title) {
this.title = title;
}
@NonNull

View file

@ -7,6 +7,7 @@ import com.faendir.acra.sql.model.App;
import com.faendir.acra.sql.model.Permission;
import com.faendir.acra.sql.model.User;
import com.faendir.acra.sql.user.UserManager;
import com.faendir.acra.ui.view.app.AppView;
import com.faendir.acra.ui.view.base.ConfigurationLabel;
import com.faendir.acra.ui.view.base.MyGrid;
import com.faendir.acra.ui.view.base.NamedView;

View file

@ -1,17 +1,18 @@
package com.faendir.acra.ui.view;
package com.faendir.acra.ui.view.app;
import com.faendir.acra.sql.data.AppRepository;
import com.faendir.acra.sql.model.App;
import com.faendir.acra.sql.model.Permission;
import com.faendir.acra.ui.annotation.RequiresAppPermission;
import com.faendir.acra.ui.view.app.tabs.BugTab;
import com.faendir.acra.ui.view.app.tabs.DeObfuscationTab;
import com.faendir.acra.ui.view.app.tabs.PropertiesTab;
import com.faendir.acra.ui.view.app.tabs.ReportTab;
import com.faendir.acra.ui.view.app.tabs.StatisticsTab;
import com.faendir.acra.ui.view.base.MyTabSheet;
import com.faendir.acra.ui.view.base.ParametrizedNamedView;
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.ReportTab;
import com.faendir.acra.ui.view.tabs.StatisticsTab;
import com.faendir.acra.util.Style;
import com.faendir.acra.util.Utils;
import com.vaadin.spring.annotation.SpringView;
import com.vaadin.ui.VerticalLayout;
import org.springframework.beans.factory.annotation.Autowired;
@ -20,6 +21,8 @@ import org.springframework.data.util.Pair;
import org.springframework.lang.NonNull;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Lukas
@ -30,7 +33,7 @@ import java.util.Optional;
public class AppView extends ParametrizedNamedView<Pair<App, String>> {
@NonNull private final AppRepository appRepository;
@NonNull private final ApplicationContext applicationContext;
private MyTabSheet tabSheet;
private MyTabSheet<App> tabSheet;
@Autowired
public AppView(@NonNull AppRepository appRepository, @NonNull ApplicationContext applicationContext) {
@ -58,8 +61,11 @@ public class AppView extends ParametrizedNamedView<Pair<App, String>> {
Optional<App> appOptional = appRepository.findByEncodedId(parameters[0]);
if (appOptional.isPresent()) {
App app = appOptional.get();
tabSheet = new MyTabSheet(app, getNavigationManager(), applicationContext, BugTab.class, ReportTab.class, StatisticsTab.class, DeObfuscationTab.class,
PropertiesTab.class);
tabSheet = new MyTabSheet<>(app, getNavigationManager(), Stream.of(BugTab.class, ReportTab.class, StatisticsTab.class, DeObfuscationTab.class, PropertiesTab.class)
.map(clazz -> Utils.getBeanIfPermissionGranted(applicationContext, app, clazz))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList()));
if (parameters.length == 1) {
return Pair.of(app, tabSheet.getCaptions().get(0));
} else if (tabSheet.getCaptions().contains(parameters[1])) {

View file

@ -1,34 +1,44 @@
package com.faendir.acra.ui.view.tabs;
package com.faendir.acra.ui.view.app.tabs;
import com.faendir.acra.dataprovider.BufferedDataProvider;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.sql.data.BugRepository;
import com.faendir.acra.sql.data.ReportRepository;
import com.faendir.acra.sql.model.App;
import com.faendir.acra.sql.model.Bug;
import com.faendir.acra.sql.model.Permission;
import com.faendir.acra.sql.model.Report;
import com.faendir.acra.sql.util.CountResult;
import com.faendir.acra.ui.NavigationManager;
import com.faendir.acra.ui.view.base.MyCheckBox;
import com.faendir.acra.ui.view.base.MyGrid;
import com.faendir.acra.ui.view.base.MyTabSheet;
import com.faendir.acra.ui.view.base.ReportList;
import com.faendir.acra.dataprovider.BufferedDataProvider;
import com.faendir.acra.ui.view.base.Popup;
import com.faendir.acra.ui.view.bug.BugView;
import com.faendir.acra.util.Style;
import com.faendir.acra.util.TimeSpanRenderer;
import com.vaadin.server.Sizeable;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.Grid;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Notification;
import com.vaadin.ui.RadioButtonGroup;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.renderers.ComponentRenderer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -38,12 +48,11 @@ import java.util.stream.Collectors;
*/
@SpringComponent
@ViewScope
public class BugTab implements MyTabSheet.Tab {
public class BugTab implements MyTabSheet.Tab<App> {
public static final String CAPTION = "Bugs";
@NonNull private final BugRepository bugRepository;
@NonNull private final ReportRepository reportRepository;
@NonNull private final BufferedDataProvider.Factory factory;
@Nullable private ReportList reportList;
@Autowired
public BugTab(@NonNull BugRepository bugRepository, @NonNull ReportRepository reportRepository, @NonNull BufferedDataProvider.Factory factory) {
@ -60,34 +69,51 @@ public class BugTab implements MyTabSheet.Tab {
@Override
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
VerticalLayout layout = new VerticalLayout();
HorizontalLayout header = new HorizontalLayout();
header.setWidth(100, Sizeable.Unit.PERCENTAGE);
Style.PADDING_TOP.apply(header);
layout.addComponent(header);
layout.setComponentAlignment(header, Alignment.MIDDLE_LEFT);
CheckBox hideSolved = new CheckBox("Hide solved", true);
layout.addComponent(hideSolved);
layout.setComponentAlignment(hideSolved, Alignment.MIDDLE_RIGHT);
MyGrid<Bug> bugs = new MyGrid<>(null, createDataProvider(app, true));
bugs.setSelectionMode(Grid.SelectionMode.MULTI);
hideSolved.addValueChangeListener(e -> layout.getUI().access(() -> {
Set<Bug> selection = bugs.getSelectedItems();
bugs.setDataProvider(createDataProvider(app, e.getValue()));
selection.forEach(bugs::select);
}));
Button merge = new Button("Merge bugs", e -> {
List<Bug> selectedItems = new ArrayList<>(bugRepository.loadStacktraces(bugs.getSelectedItems()));
if (selectedItems.size() > 1) {
RadioButtonGroup<String> titles = new RadioButtonGroup<>("", selectedItems.stream().map(Bug::getTitle).collect(Collectors.toList()));
titles.setSelectedItem(selectedItems.get(0).getTitle());
new Popup().setTitle("Choose title for bug group").addComponent(titles).addCreateButton(p -> {
Bug bug = selectedItems.remove(0);
bug.setTitle(titles.getSelectedItem().orElseThrow(IllegalStateException::new));
bug.getStacktraces().addAll(selectedItems.stream().flatMap(b -> b.getStacktraces().stream()).collect(Collectors.toList()));
bugRepository.save(bug);
for (Bug b : selectedItems) {
Slice<Report> reports = reportRepository.findAllByBug(b, PageRequest.of(0, Integer.MAX_VALUE));
reports.forEach(report -> report.setBug(bug));
reportRepository.saveAll(reports);
}
bugRepository.deleteAll(selectedItems);
bugs.setDataProvider(createDataProvider(app, hideSolved.getValue()));
p.close();
}).show();
} else {
Notification.show("Please select at least two bugs", Notification.Type.ERROR_MESSAGE);
}
});
header.addComponent(merge);
header.addComponent(hideSolved);
header.setComponentAlignment(hideSolved, Alignment.MIDDLE_RIGHT);
Map<Integer, Long> counts = reportRepository.countAllByBug().stream().collect(Collectors.toMap(CountResult::getGroup, CountResult::getCount));
bugs.addColumn(bug -> counts.get(bug.getId()), "Reports");
bugs.sort(bugs.addColumn(Bug::getLastReport, new TimeSpanRenderer(), "lastReport", "Latest Report"), SortDirection.DESCENDING);
bugs.addColumn(Bug::getVersionCode, "versionCode", "Version");
bugs.addColumn(bug -> bug.getStacktrace().split("\n", 2)[0], "stacktrace", "Stacktrace").setExpandRatio(1).setMinimumWidthFromContent(false);
bugs.addSelectionListener(event -> {
Optional<Bug> selection = event.getFirstSelectedItem();
ReportList reports = null;
if (selection.isPresent()) {
reports = new ReportList(app, navigationManager, reportRepository::delete,
factory.create(selection.get(), reportRepository::findAllByBug, reportRepository::countAllByBug));
reports.setSizeFull();
layout.replaceComponent(this.reportList, reports);
layout.setExpandRatio(reports, 1);
} else if (this.reportList != null) {
layout.removeComponent(this.reportList);
}
this.reportList = reports;
});
bugs.addColumn(Bug::getTitle, "stacktrace", "Stacktrace").setExpandRatio(1).setMinimumWidthFromContent(false);
bugs.addOnClickNavigation(navigationManager, BugView.class, bugItemClick -> String.valueOf(bugItemClick.getItem().getId()));
bugs.addColumn(bug -> new MyCheckBox(bug.isSolved(), SecurityUtils.hasPermission(app, Permission.Level.EDIT), e -> {
bug.setSolved(e.getValue());
bugRepository.save(bug);

View file

@ -1,4 +1,4 @@
package com.faendir.acra.ui.view.tabs;
package com.faendir.acra.ui.view.app.tabs;
import com.faendir.acra.dataprovider.BufferedDataProvider;
import com.faendir.acra.security.SecurityUtils;
@ -30,7 +30,7 @@ import org.vaadin.risto.stepper.IntStepper;
*/
@SpringComponent
@ViewScope
public class DeObfuscationTab implements MyTabSheet.Tab {
public class DeObfuscationTab implements MyTabSheet.Tab<App> {
public static final String CAPTION = "De-Obfuscation";
@NonNull private final ProguardMappingRepository mappingRepository;
@NonNull private final BufferedDataProvider.Factory factory;

View file

@ -1,4 +1,4 @@
package com.faendir.acra.ui.view.tabs;
package com.faendir.acra.ui.view.app.tabs;
import com.faendir.acra.sql.data.AppRepository;
import com.faendir.acra.sql.data.BugRepository;
@ -36,6 +36,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author Lukas
@ -44,7 +45,7 @@ import java.util.Map;
@RequiresAppPermission(Permission.Level.ADMIN)
@SpringComponent
@ViewScope
public class PropertiesTab implements MyTabSheet.Tab {
public class PropertiesTab implements MyTabSheet.Tab<App> {
public static final String CAPTION = "Properties";
@NonNull private final AppRepository appRepository;
@NonNull private final BugRepository bugRepository;
@ -99,17 +100,17 @@ public class PropertiesTab implements MyTabSheet.Tab {
"Are you sure you want to save this configuration? All bugs will be recalculated, which may take some time and will reset the 'solved' status"))
.addYesNoButtons(p -> {
app.setConfiguration(new App.Configuration(matchByMessage.getValue(), ignoreInstanceIds.getValue(), ignoreAndroidLineNumbers.getValue()));
appRepository.save(app);
App a = appRepository.save(app);
Map<String, Bug> bugs = new HashMap<>();
List<Report> reports = reportRepository.findAllByAppEager(app);
List<Report> reports = reportRepository.findAllByAppEager(a);
List<Bug> b = bugRepository.loadStacktraces(reports.stream().map(Report::getBug).distinct().collect(Collectors.toList()));
reports.forEach(report -> {
String stacktrace = Utils.generifyStacktrace(report.getStacktrace(), app.getConfiguration());
String stacktrace = Utils.generifyStacktrace(report.getStacktrace(), a.getConfiguration());
Bug bug = bugs.get(stacktrace);
if (bug == null) {
if (stacktrace.equals(report.getBug().getStacktrace())) {
bug = report.getBug();
} else {
bug = bugRepository.save(new Bug(app, stacktrace, report.getVersionCode(), report.getDate()));
bug = b.get(b.indexOf(report.getBug()));
if (!bug.getStacktraces().contains(stacktrace)) {
bug = bugRepository.save(new Bug(a, stacktrace, report.getVersionCode(), report.getDate()));
}
}
report.setBug(bug);

View file

@ -1,4 +1,4 @@
package com.faendir.acra.ui.view.tabs;
package com.faendir.acra.ui.view.app.tabs;
import com.faendir.acra.sql.data.ReportRepository;
import com.faendir.acra.sql.model.App;
@ -18,7 +18,7 @@ import org.springframework.lang.NonNull;
*/
@SpringComponent
@ViewScope
public class ReportTab implements MyTabSheet.Tab {
public class ReportTab implements MyTabSheet.Tab<App> {
@NonNull private final ReportRepository reportRepository;
@NonNull private final BufferedDataProvider.Factory factory;

View file

@ -1,4 +1,4 @@
package com.faendir.acra.ui.view.tabs;
package com.faendir.acra.ui.view.app.tabs;
import com.faendir.acra.sql.data.ReportRepository;
import com.faendir.acra.sql.model.App;
@ -43,7 +43,7 @@ import java.util.List;
*/
@SpringComponent
@ViewScope
public class StatisticsTab implements MyTabSheet.Tab {
public class StatisticsTab implements MyTabSheet.Tab<App> {
public static final String CAPTION = "Statistics";
private static final Color BACKGROUND_GRAY = new Color(0xfafafa); //vaadin gray
private static final Color BLUE = new Color(0x197de1); //vaadin blue

View file

@ -1,15 +1,13 @@
package com.faendir.acra.ui.view.base;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.sql.model.App;
import com.faendir.acra.ui.NavigationManager;
import com.faendir.acra.ui.annotation.RequiresAppPermission;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.TabSheet;
import org.springframework.context.ApplicationContext;
import org.springframework.lang.NonNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Spliterator;
import java.util.Spliterators;
@ -20,19 +18,20 @@ import java.util.stream.StreamSupport;
* @author Lukas
* @since 12.12.2017
*/
public class MyTabSheet extends TabSheet {
private final App app;
public class MyTabSheet<T> extends TabSheet {
private final T t;
private final NavigationManager navigationManager;
@SafeVarargs
public MyTabSheet(@NonNull App app, @NonNull NavigationManager navigationManager, @NonNull ApplicationContext applicationContext, Class<? extends Tab>... tabs) {
this.app = app;
public MyTabSheet(@NonNull T t, @NonNull NavigationManager navigationManager, Tab<T>... tabs) {
this(t, navigationManager, Arrays.asList(tabs));
}
public MyTabSheet(@NonNull T t, @NonNull NavigationManager navigationManager, Collection<Tab<T>> tabs) {
this.t = t;
this.navigationManager = navigationManager;
for (Class<? extends Tab> tabClass : tabs) {
RequiresAppPermission annotation = tabClass.getAnnotation(RequiresAppPermission.class);
if (annotation == null || SecurityUtils.hasPermission(app, annotation.value())) {
addTab(applicationContext.getBean(tabClass));
}
for (Tab<T> tab : tabs) {
addTab(tab);
}
}
@ -40,37 +39,34 @@ public class MyTabSheet extends TabSheet {
return StreamSupport.stream(Spliterators.spliterator(iterator(), getComponentCount(), Spliterator.ORDERED), false).map(Component::getCaption).collect(Collectors.toList());
}
public void addTab(Tab tab) {
TabWrapper wrapper = new TabWrapper(app, navigationManager, tab);
public void addTab(Tab<T> tab) {
TabWrapper<T> wrapper = new TabWrapper<>(t, navigationManager, tab);
addComponent(wrapper);
addSelectedTabChangeListener(wrapper);
}
public void setInitialTab(String caption) {
StreamSupport.stream(spliterator(), false)
.filter(c -> c.getCaption().equals(caption))
.findAny()
.ifPresent(component -> {
if (getSelectedTab() == component && component instanceof SelectedTabChangeListener) {
((SelectedTabChangeListener) component).selectedTabChange(new SelectedTabChangeEvent(this, true));
}
setSelectedTab(component);
});
StreamSupport.stream(spliterator(), false).filter(c -> c.getCaption().equals(caption)).findAny().ifPresent(component -> {
if (getSelectedTab() == component && component instanceof SelectedTabChangeListener) {
((SelectedTabChangeListener) component).selectedTabChange(new SelectedTabChangeEvent(this, true));
}
setSelectedTab(component);
});
}
public interface Tab {
Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager);
public interface Tab<T> {
Component createContent(@NonNull T t, @NonNull NavigationManager navigationManager);
String getCaption();
}
private static class TabWrapper extends CustomComponent implements SelectedTabChangeListener {
private final App app;
private static class TabWrapper<T> extends CustomComponent implements SelectedTabChangeListener {
private final T t;
private final NavigationManager navigationManager;
private final Tab tab;
private final Tab<T> tab;
private TabWrapper(@NonNull App app, @NonNull NavigationManager navigationManager, @NonNull Tab tab) {
this.app = app;
private TabWrapper(@NonNull T t, @NonNull NavigationManager navigationManager, @NonNull Tab<T> tab) {
this.t = t;
this.navigationManager = navigationManager;
this.tab = tab;
setSizeFull();
@ -79,7 +75,7 @@ public class MyTabSheet extends TabSheet {
@Override
public void selectedTabChange(@NonNull SelectedTabChangeEvent event) {
if (this == event.getTabSheet().getSelectedTab()) {
setCompositionRoot(tab.createContent(app, navigationManager));
setCompositionRoot(tab.createContent(t, navigationManager));
} else {
setCompositionRoot(null);
}

View file

@ -6,7 +6,7 @@ import com.faendir.acra.sql.model.App;
import com.faendir.acra.sql.model.Permission;
import com.faendir.acra.sql.model.Report;
import com.faendir.acra.ui.NavigationManager;
import com.faendir.acra.ui.view.ReportView;
import com.faendir.acra.ui.view.report.ReportView;
import com.faendir.acra.util.TimeSpanRenderer;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.ui.Label;

View file

@ -0,0 +1,91 @@
package com.faendir.acra.ui.view.bug;
import com.faendir.acra.sql.data.BugRepository;
import com.faendir.acra.sql.model.Bug;
import com.faendir.acra.sql.model.Permission;
import com.faendir.acra.ui.annotation.RequiresAppPermission;
import com.faendir.acra.ui.view.base.MyTabSheet;
import com.faendir.acra.ui.view.base.ParametrizedNamedView;
import com.faendir.acra.ui.view.bug.tabs.ReportTab;
import com.faendir.acra.ui.view.bug.tabs.StackTraceTab;
import com.faendir.acra.util.Style;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.spring.annotation.SpringView;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Panel;
import com.vaadin.ui.VerticalLayout;
import org.ocpsoft.prettytime.PrettyTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import java.util.Optional;
/**
* @author Lukas
* @since 21.03.2018
*/
@SpringView(name = "bug")
@RequiresAppPermission(Permission.Level.VIEW)
public class BugView extends ParametrizedNamedView<Bug> {
@NonNull private final BugRepository bugRepository;
@NonNull private final ApplicationContext applicationContext;
private MyTabSheet<Bug> tabSheet;
@Autowired
public BugView(@NonNull BugRepository bugRepository, @NonNull ApplicationContext applicationContext) {
super(Bug::getApp);
this.bugRepository = bugRepository;
this.applicationContext = applicationContext;
}
@Nullable
@Override
protected Bug validateAndParseFragment(@NonNull String fragment) {
try {
String[] parameters = fragment.split("/");
if (parameters.length > 0) {
Optional<Bug> bugOptional = bugRepository.findByIdEager(Integer.parseInt(parameters[0]));
if (bugOptional.isPresent()) {
Bug bug = bugOptional.get();
tabSheet = new MyTabSheet<>(bug, getNavigationManager(), applicationContext.getBean(ReportTab.class), applicationContext.getBean(StackTraceTab.class));
if (parameters.length == 1) {
tabSheet.setInitialTab(tabSheet.getCaptions().get(0));
} else if (tabSheet.getCaptions().contains(parameters[1])) {
tabSheet.setInitialTab(parameters[1]);
}
return bug;
}
}
} catch (IllegalArgumentException ignored) {
}
return null;
}
@Override
protected void enter(@NonNull Bug bug) {
GridLayout summaryGrid = new GridLayout(2, 1);
summaryGrid.addComponents(new Label("Title", ContentMode.PREFORMATTED), new Label(bug.getTitle(), ContentMode.PREFORMATTED));
summaryGrid.addComponents(new Label("Version", ContentMode.PREFORMATTED), new Label(String.valueOf(bug.getVersionCode()), ContentMode.PREFORMATTED));
summaryGrid.addComponents(new Label("Last Report", ContentMode.PREFORMATTED), new Label(new PrettyTime().format(bug.getLastReport()), ContentMode.PREFORMATTED));
summaryGrid.setDefaultComponentAlignment(Alignment.MIDDLE_LEFT);
summaryGrid.setSizeFull();
Panel summary = new Panel(summaryGrid);
summary.setCaption("Summary");
tabSheet.setSizeFull();
tabSheet.addSelectedTabChangeListener(e -> getNavigationManager().updatePageParameters(bug.getId() + "/" + e.getTabSheet().getSelectedTab().getCaption()));
VerticalLayout layout = new VerticalLayout(summary, tabSheet);
layout.setSizeFull();
layout.setExpandRatio(tabSheet, 1);
Style.NO_PADDING.apply(layout);
Panel root = new Panel(layout);
root.setSizeFull();
Style.apply(root, Style.NO_BACKGROUND, Style.NO_BORDER);
setCompositionRoot(root);
Style.apply(this, Style.PADDING_LEFT, Style.PADDING_RIGHT, Style.PADDING_BOTTOM);
setSizeFull();
}
}

View file

@ -0,0 +1,43 @@
package com.faendir.acra.ui.view.bug.tabs;
import com.faendir.acra.dataprovider.BufferedDataProvider;
import com.faendir.acra.sql.data.ReportRepository;
import com.faendir.acra.sql.model.Bug;
import com.faendir.acra.ui.NavigationManager;
import com.faendir.acra.ui.view.base.MyTabSheet;
import com.faendir.acra.ui.view.base.ReportList;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
/**
* @author Lukas
* @since 21.03.2018
*/
@SpringComponent("bugReportTab")
@ViewScope
public class ReportTab implements MyTabSheet.Tab<Bug> {
@NonNull private final ReportRepository reportRepository;
@NonNull private final BufferedDataProvider.Factory factory;
@Autowired
public ReportTab(@NonNull ReportRepository reportRepository, @NonNull BufferedDataProvider.Factory factory) {
this.reportRepository = reportRepository;
this.factory = factory;
}
@Override
public Component createContent(@NonNull Bug bug, @NonNull NavigationManager navigationManager) {
Component content = new ReportList(bug.getApp(), navigationManager, reportRepository::delete,
factory.create(bug, reportRepository::findAllByBug, reportRepository::countAllByBug));
content.setSizeFull();
return content;
}
@Override
public String getCaption() {
return ReportList.CAPTION;
}
}

View file

@ -0,0 +1,52 @@
package com.faendir.acra.ui.view.bug.tabs;
import com.faendir.acra.sql.data.ProguardMappingRepository;
import com.faendir.acra.sql.model.Bug;
import com.faendir.acra.sql.model.ProguardMapping;
import com.faendir.acra.ui.NavigationManager;
import com.faendir.acra.ui.view.base.MyTabSheet;
import com.faendir.acra.util.Utils;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Accordion;
import com.vaadin.ui.Component;
import com.vaadin.ui.Label;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import java.util.Optional;
/**
* @author Lukas
* @since 21.03.2018
*/
@SpringComponent
@ViewScope
public class StackTraceTab implements MyTabSheet.Tab<Bug>{
@NonNull private final ProguardMappingRepository mappingRepository;
@Autowired
public StackTraceTab(@NonNull ProguardMappingRepository mappingRepository) {
this.mappingRepository = mappingRepository;
}
@Override
public Component createContent(@NonNull Bug bug, @NonNull NavigationManager navigationManager) {
Optional<ProguardMapping> mapping = mappingRepository.findById(bug.getApp(), bug.getVersionCode());
Accordion accordion = new Accordion();
for (String stacktrace : bug.getStacktraces()){
if(mapping.isPresent()){
stacktrace = Utils.retrace(stacktrace, mapping.get().getMappings());
}
accordion.addTab(new Label(stacktrace, ContentMode.PREFORMATTED)).setCaption(stacktrace.split("\n", 2)[0]);
}
accordion.setSizeFull();
return accordion;
}
@Override
public String getCaption() {
return "Stacktraces";
}
}

View file

@ -1,4 +1,4 @@
package com.faendir.acra.ui.view;
package com.faendir.acra.ui.view.report;
import com.diffplug.common.base.Errors;
import com.diffplug.common.base.Throwing;

View file

@ -1,9 +1,12 @@
package com.faendir.acra.util;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.sql.model.App;
import com.faendir.acra.ui.annotation.RequiresAppPermission;
import com.vaadin.ui.UI;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.lang.NonNull;
import org.springframework.web.util.UriComponentsBuilder;
import proguard.retrace.ReTrace;
@ -18,6 +21,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@ -93,4 +97,12 @@ public final class Utils {
}
return output.toString();
}
public static <T> Optional<T> getBeanIfPermissionGranted(@NonNull ApplicationContext applicationContext, @NonNull App app, @NonNull Class<T> clazz){
RequiresAppPermission annotation = clazz.getAnnotation(RequiresAppPermission.class);
if (annotation == null || SecurityUtils.hasPermission(app, annotation.value())) {
return Optional.of(applicationContext.getBean(clazz));
}
return Optional.empty();
}
}