bug improvements
This commit is contained in:
parent
9301b3281e
commit
4311a672dc
19 changed files with 345 additions and 92 deletions
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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])) {
|
|
@ -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);
|
|
@ -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;
|
|
@ -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);
|
|
@ -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;
|
||||
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
91
src/main/java/com/faendir/acra/ui/view/bug/BugView.java
Normal file
91
src/main/java/com/faendir/acra/ui/view/bug/BugView.java
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue