mongod pre-delete

This commit is contained in:
F43nd1r 2017-12-12 01:13:48 +01:00
parent b368790358
commit 694d48b352
15 changed files with 212 additions and 102 deletions

View file

@ -8,10 +8,10 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.gridfs.GridFsTemplate; import org.springframework.data.mongodb.gridfs.GridFsTemplate;
@ -31,7 +31,7 @@ public class MongoConfiguration {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoFactory); DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoFactory);
MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mongoMappingContext); MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
mongoConverter.setMapKeyDotReplacement("%&&%"); mongoConverter.setMapKeyDotReplacement("%&&%");
mongoConverter.setCustomConversions(new CustomConversions(Arrays.asList(new StacktraceElementReadConverter(), new StacktraceElementWriteConverter()))); mongoConverter.setCustomConversions(new MongoCustomConversions(Arrays.asList(new StacktraceElementReadConverter(), new StacktraceElementWriteConverter())));
mongoConverter.afterPropertiesSet(); mongoConverter.afterPropertiesSet();
return mongoConverter; return mongoConverter;
} }

View file

@ -2,6 +2,8 @@ package com.faendir.acra.mongod.data;
import com.faendir.acra.mongod.model.Bug; import com.faendir.acra.mongod.model.Bug;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List; import java.util.List;
@ -11,8 +13,13 @@ import java.util.List;
* @since 31.05.2017 * @since 31.05.2017
*/ */
interface BugRepository extends MongoRepository<Bug, String> { interface BugRepository extends MongoRepository<Bug, String> {
@NotNull @NotNull List<Bug> findByApp(String app);
List<Bug> findByApp(String app);
@NotNull @NotNull Page<Bug> findAllByApp(String app, Pageable pageable);
List<Bug> findByReportIdsContains(String reportId);
@NotNull Page<Bug> findAllByAppAndSolvedIsFalse(String app, Pageable pageable);
int countAllByApp(String app);
@NotNull List<Bug> findByReportIdsContains(String reportId);
} }

View file

@ -6,8 +6,10 @@ import com.faendir.acra.mongod.model.ParsedException;
import com.faendir.acra.mongod.model.ProguardMapping; import com.faendir.acra.mongod.model.ProguardMapping;
import com.faendir.acra.mongod.model.Report; import com.faendir.acra.mongod.model.Report;
import com.faendir.acra.mongod.model.ReportInfo; import com.faendir.acra.mongod.model.ReportInfo;
import com.faendir.acra.mongod.util.BufferedMongoDataProvider;
import com.mongodb.BasicDBObjectBuilder; import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.gridfs.GridFSDBFile; import com.mongodb.client.gridfs.model.GridFSFile;
import com.vaadin.data.provider.DataProvider;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -15,12 +17,11 @@ import org.json.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.data.mongodb.gridfs.GridFsTemplate; import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils; import org.springframework.util.Base64Utils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -35,9 +36,12 @@ import java.io.StringWriter;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -47,10 +51,6 @@ import java.util.stream.Stream;
*/ */
@Component @Component
public class DataManager { public class DataManager {
private static final String APP_REPORT_CACHE = "appReport";
private static final String APP_CACHE = "app";
private static final String BUG_REPORT_CACHE = "bugReport";
private static final String APP_BUG_CACHE = "appBug";
@NotNull private final MappingRepository mappingRepository; @NotNull private final MappingRepository mappingRepository;
@NotNull private final ReportRepository reportRepository; @NotNull private final ReportRepository reportRepository;
@NotNull private final AppRepository appRepository; @NotNull private final AppRepository appRepository;
@ -75,14 +75,12 @@ public class DataManager {
this.listenerExecutor = Executors.newSingleThreadExecutor(); this.listenerExecutor = Executors.newSingleThreadExecutor();
} }
@CacheEvict(value = APP_CACHE, allEntries = true)
public synchronized void createNewApp(@NotNull String name) { public synchronized void createNewApp(@NotNull String name) {
byte[] bytes = new byte[12]; byte[] bytes = new byte[12];
secureRandom.nextBytes(bytes); secureRandom.nextBytes(bytes);
appRepository.save(new App(name, Base64Utils.encodeToString(bytes))); appRepository.save(new App(name, Base64Utils.encodeToString(bytes)));
} }
@Cacheable(value = APP_CACHE)
@NotNull @NotNull
public List<App> getApps() { public List<App> getApps() {
return appRepository.findAll(); return appRepository.findAll();
@ -90,19 +88,19 @@ public class DataManager {
@Nullable @Nullable
public App getApp(@NotNull String id) { public App getApp(@NotNull String id) {
return appRepository.findOne(id); return appRepository.findById(id).orElse(null);
} }
@CacheEvict(value = APP_CACHE, allEntries = true)
public synchronized void deleteApp(@NotNull String id) { public synchronized void deleteApp(@NotNull String id) {
appRepository.delete(id); appRepository.deleteById(id);
getReportsForApp(id).forEach(this::deleteReport); getReportsForApp(id).forEach(this::deleteReport);
mappingRepository.delete(getMappings(id)); mappingRepository.deleteAll(getMappings(id));
} }
@NotNull @NotNull
public List<GridFSDBFile> getAttachments(@NotNull String report) { public List<Pair<GridFSFile, Supplier<GridFsResource>>> getAttachments(@NotNull String report) {
return gridFsTemplate.find(new Query(Criteria.where("metadata.reportId").is(report))); return gridFsTemplate.find(new Query(Criteria.where("metadata.reportId").is(report)))
.map(file -> Pair.of(file, (Supplier<GridFsResource>) () -> gridFsTemplate.getResource(file.getFilename()))).into(new ArrayList<>());
} }
public synchronized void addMapping(@NotNull String app, int version, @NotNull String mappings) { public synchronized void addMapping(@NotNull String app, int version, @NotNull String mappings) {
@ -111,7 +109,7 @@ public class DataManager {
@Nullable @Nullable
private ProguardMapping getMapping(@NotNull String app, int version) { private ProguardMapping getMapping(@NotNull String app, int version) {
return mappingRepository.findOne(new ProguardMapping.MetaData(app, version)); return mappingRepository.findById(new ProguardMapping.MetaData(app, version)).orElse(null);
} }
@NotNull @NotNull
@ -123,7 +121,6 @@ public class DataManager {
newReport(app, content, Collections.emptyList()); newReport(app, content, Collections.emptyList());
} }
@CacheEvict(value = APP_REPORT_CACHE, key = "#a0")
public synchronized void newReport(@NotNull String app, @NotNull JSONObject content, @NotNull List<MultipartFile> attachments) { public synchronized void newReport(@NotNull String app, @NotNull JSONObject content, @NotNull List<MultipartFile> attachments) {
Report report = reportRepository.save(new Report(content, app)); Report report = reportRepository.save(new Report(content, app));
for (MultipartFile a : attachments) { for (MultipartFile a : attachments) {
@ -137,10 +134,10 @@ public class DataManager {
notifyListeners(info); notifyListeners(info);
Bug bug = getBugs(app).stream().filter(b -> matches(b, info)).findAny().orElseGet(() -> new Bug(info.getApp(), info.getStacktrace(), info.getVersionCode())); Bug bug = getBugs(app).stream().filter(b -> matches(b, info)).findAny().orElseGet(() -> new Bug(info.getApp(), info.getStacktrace(), info.getVersionCode()));
bug.getReportIds().add(info.getId()); bug.getReportIds().add(info.getId());
bug.setLastReport(info.getDate());
saveBug(bug); saveBug(bug);
} }
@Cacheable(APP_REPORT_CACHE)
@NotNull @NotNull
public List<ReportInfo> getReportsForApp(@NotNull String app) { public List<ReportInfo> getReportsForApp(@NotNull String app) {
try (Stream<Report> stream = reportRepository.streamAllByApp(app)) { try (Stream<Report> stream = reportRepository.streamAllByApp(app)) {
@ -148,18 +145,21 @@ public class DataManager {
} }
} }
public DataProvider<ReportInfo, Void> getLazyReportsForApp(@NotNull String app) {
return BufferedMongoDataProvider.of(pageable -> reportRepository.findAllByApp(app, pageable), ReportInfo::new);
}
public long reportCountForApp(@NotNull String app) { public long reportCountForApp(@NotNull String app) {
return reportRepository.countByApp(app); return reportRepository.countByApp(app);
} }
@Nullable @Nullable
public Report getReport(@NotNull String id) { public Report getReport(@NotNull String id) {
return reportRepository.findOne(id); return reportRepository.findById(id).orElse(null);
} }
@CacheEvict(value = APP_REPORT_CACHE, key = "#a0.app")
public synchronized void deleteReport(@NotNull ReportInfo report) { public synchronized void deleteReport(@NotNull ReportInfo report) {
reportRepository.delete(report.getId()); reportRepository.deleteById(report.getId());
gridFsTemplate.delete(new Query(Criteria.where("metadata.reportId").is(report.getId()))); gridFsTemplate.delete(new Query(Criteria.where("metadata.reportId").is(report.getId())));
bugRepository.findByReportIdsContains(report.getId()).forEach(bug -> { bugRepository.findByReportIdsContains(report.getId()).forEach(bug -> {
bug.getReportIds().remove(report.getId()); bug.getReportIds().remove(report.getId());
@ -173,48 +173,54 @@ public class DataManager {
} }
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@CacheEvict(value = APP_BUG_CACHE, key = "#a0.app")
public void deleteBug(@NotNull Bug bug) { public void deleteBug(@NotNull Bug bug) {
bugRepository.delete(bug); bugRepository.delete(bug);
notifyListeners(bug); notifyListeners(bug);
} }
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@Caching(evict = {@CacheEvict(value = APP_BUG_CACHE, key = "#a0.app"), @CacheEvict(value = BUG_REPORT_CACHE, key = "#a0.id")}) public void saveBug(@NotNull Bug bug) {
@NotNull
public Bug saveBug(@NotNull Bug bug) {
Bug b = bugRepository.save(bug); Bug b = bugRepository.save(bug);
notifyListeners(bug); notifyListeners(bug);
return b;
} }
@NotNull @NotNull
@Cacheable(value = APP_BUG_CACHE)
public List<Bug> getBugs(@NotNull String app) { public List<Bug> getBugs(@NotNull String app) {
return bugRepository.findByApp(app); return bugRepository.findByApp(app);
} }
@Cacheable(value = BUG_REPORT_CACHE, key = "#a0.id") public DataProvider<Bug, Void> getLazyBugs(@NotNull String app, boolean includeSolved) {
@NotNull if (includeSolved) {
public List<ReportInfo> getReportsForBug(@NotNull Bug bug) { return BufferedMongoDataProvider.of(pageable -> bugRepository.findAllByApp(app, pageable));
try (Stream<Report> stream = reportRepository.streamAllByIdIn(bug.getReportIds())) { } else {
return stream.map(ReportInfo::new).collect(Collectors.toList()); return BufferedMongoDataProvider.of(pageable -> bugRepository.findAllByAppAndSolvedIsFalse(app, pageable));
} }
} }
public DataProvider<ReportInfo, Void> getLazyReportsForBug(@NotNull Bug bug) {
return BufferedMongoDataProvider.of(pageable -> reportRepository.findAllByIdIn(bug.getReportIds(), pageable), ReportInfo::new);
}
public long reportCountForBug(@NotNull Bug bug) { public long reportCountForBug(@NotNull Bug bug) {
return bug.getReportIds().size(); return bug.getReportIds().size();
} }
public void rebuildBugs(@NotNull String app) { public void rebuildBugs(@NotNull String app) {
getBugs(app).forEach(this::deleteBug); bugRepository.deleteAll();
List<Bug> bugs = new ArrayList<>(); Map<Bug, List<ReportInfo>> mapping = new HashMap<>();
getReportsForApp(app).forEach(reportInfo -> bugs.stream().filter(bug -> matches(bug, reportInfo)).findAny().orElseGet(() -> { getReportsForApp(app).forEach(reportInfo -> mapping.entrySet().stream().filter(entry -> matches(entry.getKey(), reportInfo)).map(Map.Entry::getValue).findAny()
Bug bug = new Bug(reportInfo.getApp(), reportInfo.getStacktrace(), reportInfo.getVersionCode()); .orElseGet(() -> {
bugs.add(bug); List<ReportInfo> list = new ArrayList<>();
return bug; mapping.put(new Bug(reportInfo.getApp(), reportInfo.getStacktrace(), reportInfo.getVersionCode()), list);
}).getReportIds().add(reportInfo.getId())); return list;
bugs.forEach(this::saveBug); })
.add(reportInfo));
mapping.forEach((bug, reports) -> {
reports.forEach(report->bug.getReportIds().add(report.getId()));
bug.setLastReport(ReportUtils.getLastReportDate(reports));
});
bugRepository.insert(mapping.keySet());
mapping.keySet().stream().findAny().ifPresent(this::notifyListeners);
} }
public boolean matches(@NotNull Bug bug, @NotNull ReportInfo info) { public boolean matches(@NotNull Bug bug, @NotNull ReportInfo info) {
@ -275,5 +281,4 @@ public class DataManager {
} }
} }
} }
} }

View file

@ -2,6 +2,8 @@ package com.faendir.acra.mongod.data;
import com.faendir.acra.mongod.model.Report; import com.faendir.acra.mongod.model.Report;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -14,10 +16,16 @@ interface ReportRepository extends MongoRepository<Report, String> {
@NotNull @NotNull
Stream<Report> streamAllByApp(String app); Stream<Report> streamAllByApp(String app);
long countByApp(String app); int countByApp(String app);
@NotNull @NotNull
Stream<Report> streamAllByIdIn(@NotNull Iterable<String> ids); Stream<Report> streamAllByIdIn(@NotNull Iterable<String> ids);
@NotNull
Page<Report> findAllByApp(String app, Pageable pageable);
@NotNull
Page<Report> findAllByIdIn(@NotNull Iterable<String> ids, Pageable pageable);
} }

View file

@ -1,11 +1,13 @@
package com.faendir.acra.mongod.model; package com.faendir.acra.mongod.model;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
/** /**
@ -14,25 +16,27 @@ import java.util.List;
*/ */
@Document @Document
public class Bug implements AppScoped { public class Bug implements AppScoped {
@NotNull private final String id; @Nullable private final String id;
@NotNull @Indexed private final String app; @NotNull @Indexed private final String app;
@NotNull private final String stacktrace; @NotNull private final String stacktrace;
private final int versionCode; private final int versionCode;
@NotNull private final List<String> reportIds; @NotNull private final List<String> reportIds;
private boolean solved; private boolean solved;
@Nullable private Date lastReport;
@PersistenceConstructor @PersistenceConstructor
private Bug(@NotNull String id, @NotNull String app, boolean solved, @NotNull String stacktrace, int versionCode, @NotNull List<String> reportIds) { private Bug(@Nullable String id, @NotNull String app, boolean solved, @NotNull String stacktrace, int versionCode, @NotNull List<String> reportIds, @Nullable Date lastReport) {
this.id = id; this.id = id;
this.app = app; this.app = app;
this.solved = solved; this.solved = solved;
this.stacktrace = stacktrace; this.stacktrace = stacktrace;
this.versionCode = versionCode; this.versionCode = versionCode;
this.reportIds = reportIds; this.reportIds = reportIds;
this.lastReport = lastReport;
} }
public Bug(@NotNull String app, @NotNull String stacktrace, int versionCode){ public Bug(@NotNull String app, @NotNull String stacktrace, int versionCode){
this("", app, false, stacktrace, versionCode, new ArrayList<>()); this(null, app, false, stacktrace, versionCode, new ArrayList<>(), null);
} }
public boolean isSolved() { public boolean isSolved() {
@ -63,8 +67,19 @@ public class Bug implements AppScoped {
return reportIds; return reportIds;
} }
@NotNull @Nullable
public String getId() { public String getId() {
return id; return id;
} }
@Nullable
public Date getLastReport() {
return lastReport;
}
public void setLastReport(@NotNull Date lastReport) {
if (this.lastReport == null || this.lastReport.before(lastReport)) {
this.lastReport = lastReport;
}
}
} }

View file

@ -48,18 +48,16 @@ public class UserManager {
@Nullable @Nullable
public User getUser(@NotNull String username) { public User getUser(@NotNull String username) {
User user = userRepository.findOne(username); Optional<User> user = userRepository.findById(username);
if (user == null && defaultUser.equals(username)) { if (!user.isPresent() && defaultUser.equals(username)) {
user = getDefaultUser(); user = Optional.of(getDefaultUser());
} }
if (user != null) { user.ifPresent(this::ensureValidPermissions);
ensureValidPermissions(user); return user.orElse(null);
}
return user;
} }
public void createUser(@NotNull String username, @NotNull String password) { public void createUser(@NotNull String username, @NotNull String password) {
if (userRepository.exists(username)) { if (userRepository.existsById(username)) {
throw new IllegalArgumentException("Username already exists"); throw new IllegalArgumentException("Username already exists");
} }
User user = new User(username, passwordEncoder.encode(password), Collections.singleton(ROLE_USER)); User user = new User(username, passwordEncoder.encode(password), Collections.singleton(ROLE_USER));

View file

@ -0,0 +1,64 @@
package com.faendir.acra.mongod.util;
import com.vaadin.data.provider.AbstractBackEndDataProvider;
import com.vaadin.data.provider.Query;
import com.vaadin.shared.data.sort.SortDirection;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Lukas
* @since 07.12.2017
*/
public class BufferedMongoDataProvider<T,R> extends AbstractBackEndDataProvider<R, Void> {
private static final int PAGE_SIZE = 32;
private final Function<Pageable, Page<T>> getter;
private final Function<T, R> transformer;
public static <T, R> BufferedMongoDataProvider<T, R> of(Function<Pageable, Page<T>> getter, Function<T, R> transformer) {
return new BufferedMongoDataProvider<>(getter, transformer);
}
public static <R> BufferedMongoDataProvider<R,R> of(Function<Pageable, Page<R>> getter){
return of(getter, Function.identity());
}
private BufferedMongoDataProvider(Function<Pageable, Page<T>> getter, Function<T, R> transformer){
this.getter = getter;
this.transformer = transformer;
}
@Override
protected Stream<R> fetchFromBackEnd(Query<R, Void> query) {
Sort sort = Sort.by(query.getSortOrders().stream()
.map(s -> new Sort.Order(s.getDirection() == SortDirection.ASCENDING ? Sort.Direction.ASC : Sort.Direction.DESC, s.getSorted()))
.collect(Collectors.toList()));
Page<T> page = getter.apply(PageRequest.of(query.getOffset() / PAGE_SIZE, PAGE_SIZE, sort));
if (!page.hasContent()) return Stream.empty();
List<T> content = page.getContent();
int ignore = query.getOffset() % PAGE_SIZE;
int size = content.size() - ignore;
Stream<T> result = content.stream().skip(ignore);
while (size < query.getLimit() && page.hasNext()) {
page = getter.apply(page.nextPageable());
if (page.hasContent()) {
content = page.getContent();
size += content.size();
result = Stream.concat(result, content.stream());
}
}
return result.map(transformer);
}
@Override
protected int sizeInBackEnd(Query<R, Void> query) {
return (int) getter.apply(PageRequest.of(0, 1)).getTotalElements();
}
}

View file

@ -55,6 +55,7 @@ public class BackendUI extends UI {
} else { } else {
showLogin(); showLogin();
} }
//applicationContext.getBean(ToSqlMigrator.class).migrate();
} }
private void login(@NotNull String username, @NotNull String password) { private void login(@NotNull String username, @NotNull String password) {

View file

@ -6,7 +6,7 @@ import com.faendir.acra.mongod.model.Report;
import com.faendir.acra.ui.view.annotation.RequiresAppPermission; import com.faendir.acra.ui.view.annotation.RequiresAppPermission;
import com.faendir.acra.ui.view.base.NamedView; import com.faendir.acra.ui.view.base.NamedView;
import com.faendir.acra.util.Style; import com.faendir.acra.util.Style;
import com.mongodb.gridfs.GridFSDBFile; import com.mongodb.client.gridfs.model.GridFSFile;
import com.vaadin.navigator.ViewChangeListener; import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.server.FileDownloader; import com.vaadin.server.FileDownloader;
import com.vaadin.server.StreamResource; import com.vaadin.server.StreamResource;
@ -23,12 +23,16 @@ import com.vaadin.ui.VerticalLayout;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.data.util.Pair;
import java.io.IOException;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
@ -81,10 +85,16 @@ public class ReportView extends NamedView {
public void enter(@NotNull ViewChangeListener.ViewChangeEvent event) { public void enter(@NotNull ViewChangeListener.ViewChangeEvent event) {
Report report = dataManager.getReport(event.getParameters()); Report report = dataManager.getReport(event.getParameters());
assert report != null; assert report != null;
List<GridFSDBFile> attachmentList = dataManager.getAttachments(report.getId()); List<Pair<GridFSFile, Supplier<GridFsResource>>> attachmentList = dataManager.getAttachments(report.getId());
HorizontalLayout attachments = new HorizontalLayout(attachmentList.stream().map(file -> { HorizontalLayout attachments = new HorizontalLayout(attachmentList.stream().map(file -> {
Button button = new Button(file.getFilename()); Button button = new Button(file.getFirst().getFilename());
new FileDownloader(new StreamResource(file::getInputStream, file.getFilename())).extend(button); new FileDownloader(new StreamResource(() -> {
try {
return file.getSecond().get().getInputStream();
} catch (IOException e) {
return null;
}
}, file.getFirst().getFilename())).extend(button);
return button; return button;
}).toArray(Component[]::new)); }).toArray(Component[]::new));
Style.apply(attachments, Style.MARGIN_BOTTOM, Style.MARGIN_TOP, Style.MARGIN_LEFT, Style.MARGIN_RIGHT); Style.apply(attachments, Style.MARGIN_BOTTOM, Style.MARGIN_TOP, Style.MARGIN_LEFT, Style.MARGIN_RIGHT);

View file

@ -1,6 +1,7 @@
package com.faendir.acra.ui.view.base; package com.faendir.acra.ui.view.base;
import com.vaadin.data.ValueProvider; import com.vaadin.data.ValueProvider;
import com.vaadin.data.provider.DataProvider;
import com.vaadin.ui.Grid; import com.vaadin.ui.Grid;
import com.vaadin.ui.renderers.AbstractRenderer; import com.vaadin.ui.renderers.AbstractRenderer;
import com.vaadin.ui.renderers.TextRenderer; import com.vaadin.ui.renderers.TextRenderer;
@ -14,6 +15,10 @@ import java.util.Collection;
* @since 14.05.2017 * @since 14.05.2017
*/ */
public class MyGrid<T> extends Grid<T> { public class MyGrid<T> extends Grid<T> {
public <F> MyGrid(String caption, DataProvider<T, F> dataProvider) {
super(caption, dataProvider);
setSizeFull();
}
public MyGrid(@Nullable String caption, @NotNull Collection<T> items) { public MyGrid(@Nullable String caption, @NotNull Collection<T> items) {
super(caption, items); super(caption, items);
@ -27,7 +32,18 @@ public class MyGrid<T> extends Grid<T> {
@NotNull @NotNull
public <R> Grid.Column<T, R> addColumn(@NotNull ValueProvider<T, R> valueProvider, @NotNull AbstractRenderer<? super T, ? super R> renderer, @NotNull String caption) { public <R> Grid.Column<T, R> addColumn(@NotNull ValueProvider<T, R> valueProvider, @NotNull AbstractRenderer<? super T, ? super R> renderer, @NotNull String caption) {
return addColumn(valueProvider, renderer).setId(caption).setCaption(caption); return addColumn(valueProvider, renderer).setId(caption).setCaption(caption).setSortable(false);
}
@NotNull
public <R> Grid.Column<T, R> addColumn(@NotNull ValueProvider<T, R> valueProvider, @NotNull String id, @NotNull String caption) {
return addColumn(valueProvider, new TextRenderer(), id, caption);
}
@NotNull
public <R> Grid.Column<T, R> addColumn(@NotNull ValueProvider<T, R> valueProvider, @NotNull AbstractRenderer<? super T, ? super R> renderer, @NotNull String id,
@NotNull String caption) {
return addColumn(valueProvider, renderer).setId(id).setCaption(caption);
} }
@Override @Override

View file

@ -7,13 +7,12 @@ import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.NavigationManager; import com.faendir.acra.ui.NavigationManager;
import com.faendir.acra.ui.view.ReportView; import com.faendir.acra.ui.view.ReportView;
import com.faendir.acra.util.TimeSpanRenderer; import com.faendir.acra.util.TimeSpanRenderer;
import com.vaadin.data.provider.DataProvider;
import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.ui.renderers.ButtonRenderer; import com.vaadin.ui.renderers.ButtonRenderer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
/** /**
* @author Lukas * @author Lukas
@ -21,22 +20,22 @@ import java.util.function.Supplier;
*/ */
public class ReportList extends MyGrid<ReportInfo> implements DataManager.Listener<ReportInfo> { public class ReportList extends MyGrid<ReportInfo> implements DataManager.Listener<ReportInfo> {
public static final String CAPTION = "Reports"; public static final String CAPTION = "Reports";
@NotNull private final Supplier<List<ReportInfo>> reportSupplier; @NotNull private final DataProvider<ReportInfo, Void> reportSupplier;
@NotNull private final Function<ReportInfo, Boolean> relevanceFunction; @NotNull private final Function<ReportInfo, Boolean> relevanceFunction;
public ReportList(String app, @NotNull NavigationManager navigationManager, @NotNull DataManager dataManager, @NotNull Supplier<List<ReportInfo>> reportSupplier, public ReportList(String app, @NotNull NavigationManager navigationManager, @NotNull DataManager dataManager, @NotNull DataProvider<ReportInfo, Void> reportSupplier,
@NotNull Function<ReportInfo, Boolean> relevanceFunction) { @NotNull Function<ReportInfo, Boolean> relevanceFunction) {
super(CAPTION, reportSupplier.get()); super(CAPTION, reportSupplier);
setId(CAPTION); setId(CAPTION);
this.reportSupplier = reportSupplier; this.reportSupplier = reportSupplier;
this.relevanceFunction = relevanceFunction; this.relevanceFunction = relevanceFunction;
setWidth(100, Unit.PERCENTAGE); setWidth(100, Unit.PERCENTAGE);
setSelectionMode(SelectionMode.NONE); setSelectionMode(SelectionMode.NONE);
sort(addColumn(ReportInfo::getDate, new TimeSpanRenderer(), "Date"), SortDirection.DESCENDING); sort(addColumn(ReportInfo::getDate, new TimeSpanRenderer(), "content.map.USER_CRASH_DATE", "Date"), SortDirection.DESCENDING);
addColumn(ReportInfo::getVersionCode, "App Version"); addColumn(ReportInfo::getVersionCode, "content.map.APP_VERSION_CODE","App Version");
addColumn(ReportInfo::getAndroidVersion, "Android Version"); addColumn(ReportInfo::getAndroidVersion, "content.map.ANDROID_VERSION","Android Version");
addColumn(ReportInfo::getPhoneModel, "Device"); addColumn(ReportInfo::getPhoneModel, "content.map.PHONE_MODEL","Device");
addColumn(report -> report.getStacktrace().split("\n", 2)[0], "Stacktrace").setExpandRatio(1); addColumn(report -> report.getStacktrace().split("\n", 2)[0], "content.map.STACK_TRACE","Stacktrace").setExpandRatio(1);
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) { if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
addColumn(report -> "Delete", new ButtonRenderer<>(e -> dataManager.deleteReport(e.getItem()))); addColumn(report -> "Delete", new ButtonRenderer<>(e -> dataManager.deleteReport(e.getItem())));
} }
@ -53,6 +52,6 @@ public class ReportList extends MyGrid<ReportInfo> implements DataManager.Listen
} }
private void setItems() { private void setItems() {
getUI().access(() -> setItems(reportSupplier.get())); getUI().access(reportSupplier::refreshAll);
} }
} }

View file

@ -1,7 +1,6 @@
package com.faendir.acra.ui.view.tabs; package com.faendir.acra.ui.view.tabs;
import com.faendir.acra.mongod.data.DataManager; import com.faendir.acra.mongod.data.DataManager;
import com.faendir.acra.mongod.data.ReportUtils;
import com.faendir.acra.mongod.model.AppScoped; import com.faendir.acra.mongod.model.AppScoped;
import com.faendir.acra.mongod.model.Bug; import com.faendir.acra.mongod.model.Bug;
import com.faendir.acra.mongod.model.Permission; import com.faendir.acra.mongod.model.Permission;
@ -17,13 +16,12 @@ import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.ui.Alignment; import com.vaadin.ui.Alignment;
import com.vaadin.ui.CheckBox; import com.vaadin.ui.CheckBox;
import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.renderers.ComponentRenderer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
/** /**
* @author Lukas * @author Lukas
@ -46,15 +44,15 @@ public class BugTab extends VerticalLayout implements DataManager.Listener<AppSc
hideSolved.addValueChangeListener(e -> setItems()); hideSolved.addValueChangeListener(e -> setItems());
addComponent(hideSolved); addComponent(hideSolved);
setComponentAlignment(hideSolved, Alignment.MIDDLE_RIGHT); setComponentAlignment(hideSolved, Alignment.MIDDLE_RIGHT);
bugs = new MyGrid<>(null, getBugs()); bugs = new MyGrid<>(null, dataManager.getLazyBugs(app, false));
bugs.setWidth(100, Unit.PERCENTAGE); bugs.setWidth(100, Unit.PERCENTAGE);
bugs.addColumn(dataManager::reportCountForBug, "Reports"); bugs.addColumn(bug -> bug.getReportIds().size(), "Reports");
bugs.sort(bugs.addColumn(bug -> ReportUtils.getLastReportDate(dataManager.getReportsForBug(bug)), new TimeSpanRenderer(), "Latest Report"), SortDirection.DESCENDING); bugs.sort(bugs.addColumn(Bug::getLastReport, new TimeSpanRenderer(), "lastReport","Latest Report"), SortDirection.DESCENDING);
bugs.addColumn(Bug::getVersionCode, "Version"); bugs.addColumn(Bug::getVersionCode, "versionCode","Version");
bugs.addColumn(bug -> bug.getStacktrace().split("\n", 2)[0], "Stacktrace").setExpandRatio(1); bugs.addColumn(bug -> bug.getStacktrace().split("\n", 2)[0], "stacktrace","Stacktrace").setExpandRatio(1);
bugs.addSelectionListener(this::handleBugSelection); bugs.addSelectionListener(this::handleBugSelection);
bugs.addComponentColumn(bug -> new MyCheckBox(bug.isSolved(), SecurityUtils.hasPermission(app, Permission.Level.EDIT), e -> dataManager.setBugSolved(bug, e.getValue()))) bugs.addColumn(bug -> new MyCheckBox(bug.isSolved(), SecurityUtils.hasPermission(app, Permission.Level.EDIT), e -> dataManager.setBugSolved(bug, e.getValue())),
.setCaption("Solved"); new ComponentRenderer(), "Solved");
addComponent(bugs); addComponent(bugs);
Style.NO_PADDING.apply(this); Style.NO_PADDING.apply(this);
setCaption(CAPTION); setCaption(CAPTION);
@ -66,7 +64,7 @@ public class BugTab extends VerticalLayout implements DataManager.Listener<AppSc
Optional<Bug> selection = e.getFirstSelectedItem(); Optional<Bug> selection = e.getFirstSelectedItem();
ReportList reportList = null; ReportList reportList = null;
if (selection.isPresent()) { if (selection.isPresent()) {
reportList = new ReportList(app, navigationManager, dataManager, () -> dataManager.getReportsForBug(selection.get()), reportList = new ReportList(app, navigationManager, dataManager, dataManager.getLazyReportsForBug(selection.get()),
reportInfo -> dataManager.matches(selection.get(), reportInfo)); reportInfo -> dataManager.matches(selection.get(), reportInfo));
replaceComponent(this.reportList, reportList); replaceComponent(this.reportList, reportList);
} else if (this.reportList != null) { } else if (this.reportList != null) {
@ -85,17 +83,8 @@ public class BugTab extends VerticalLayout implements DataManager.Listener<AppSc
private void setItems() { private void setItems() {
getUI().access(() -> { getUI().access(() -> {
Set<Bug> selection = bugs.getSelectedItems(); Set<Bug> selection = bugs.getSelectedItems();
bugs.setItems(getBugs()); bugs.setDataProvider(dataManager.getLazyBugs(app, !hideSolved.getValue()));
selection.forEach(bugs::select); selection.forEach(bugs::select);
}); });
} }
@NotNull
private List<Bug> getBugs() {
List<Bug> bugs = dataManager.getBugs(app);
if (hideSolved.getValue()) {
return bugs.stream().filter(bug -> !bug.isSolved()).collect(Collectors.toList());
}
return bugs;
}
} }

View file

@ -11,6 +11,6 @@ import org.jetbrains.annotations.NotNull;
*/ */
public class ReportTab extends ReportList { public class ReportTab extends ReportList {
public ReportTab(@NotNull String app, @NotNull NavigationManager navigationManager, @NotNull DataManager dataManager) { public ReportTab(@NotNull String app, @NotNull NavigationManager navigationManager, @NotNull DataManager dataManager) {
super(app, navigationManager, dataManager, () -> dataManager.getReportsForApp(app), reportInfo -> reportInfo.getApp().equals(app)); super(app, navigationManager, dataManager, dataManager.getLazyReportsForApp(app), reportInfo -> reportInfo.getApp().equals(app));
} }
} }

View file

@ -13,8 +13,6 @@ import java.util.Date;
* @since 26.05.2017 * @since 26.05.2017
*/ */
public class TimeSpanRenderer extends TextRenderer { public class TimeSpanRenderer extends TextRenderer {
public TimeSpanRenderer() {
}
@Override @Override
public JsonValue encode(@Nullable Object value) { public JsonValue encode(@Nullable Object value) {

View file

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