mongod pre-delete
This commit is contained in:
parent
b368790358
commit
694d48b352
15 changed files with 212 additions and 102 deletions
|
@ -8,10 +8,10 @@ import org.springframework.context.annotation.Bean;
|
|||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
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.DefaultDbRefResolver;
|
||||
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.gridfs.GridFsTemplate;
|
||||
|
||||
|
@ -31,7 +31,7 @@ public class MongoConfiguration {
|
|||
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoFactory);
|
||||
MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
|
||||
mongoConverter.setMapKeyDotReplacement("%&&%");
|
||||
mongoConverter.setCustomConversions(new CustomConversions(Arrays.asList(new StacktraceElementReadConverter(), new StacktraceElementWriteConverter())));
|
||||
mongoConverter.setCustomConversions(new MongoCustomConversions(Arrays.asList(new StacktraceElementReadConverter(), new StacktraceElementWriteConverter())));
|
||||
mongoConverter.afterPropertiesSet();
|
||||
return mongoConverter;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package com.faendir.acra.mongod.data;
|
|||
|
||||
import com.faendir.acra.mongod.model.Bug;
|
||||
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 java.util.List;
|
||||
|
@ -11,8 +13,13 @@ import java.util.List;
|
|||
* @since 31.05.2017
|
||||
*/
|
||||
interface BugRepository extends MongoRepository<Bug, String> {
|
||||
@NotNull
|
||||
List<Bug> findByApp(String app);
|
||||
@NotNull
|
||||
List<Bug> findByReportIdsContains(String reportId);
|
||||
@NotNull List<Bug> findByApp(String app);
|
||||
|
||||
@NotNull Page<Bug> findAllByApp(String app, Pageable pageable);
|
||||
|
||||
@NotNull Page<Bug> findAllByAppAndSolvedIsFalse(String app, Pageable pageable);
|
||||
|
||||
int countAllByApp(String app);
|
||||
|
||||
@NotNull List<Bug> findByReportIdsContains(String reportId);
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ import com.faendir.acra.mongod.model.ParsedException;
|
|||
import com.faendir.acra.mongod.model.ProguardMapping;
|
||||
import com.faendir.acra.mongod.model.Report;
|
||||
import com.faendir.acra.mongod.model.ReportInfo;
|
||||
import com.faendir.acra.mongod.util.BufferedMongoDataProvider;
|
||||
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.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
@ -15,12 +17,11 @@ import org.json.JSONObject;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.Query;
|
||||
import org.springframework.data.mongodb.gridfs.GridFsResource;
|
||||
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
@ -35,9 +36,12 @@ import java.io.StringWriter;
|
|||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -47,10 +51,6 @@ import java.util.stream.Stream;
|
|||
*/
|
||||
@Component
|
||||
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 ReportRepository reportRepository;
|
||||
@NotNull private final AppRepository appRepository;
|
||||
|
@ -75,14 +75,12 @@ public class DataManager {
|
|||
this.listenerExecutor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
@CacheEvict(value = APP_CACHE, allEntries = true)
|
||||
public synchronized void createNewApp(@NotNull String name) {
|
||||
byte[] bytes = new byte[12];
|
||||
secureRandom.nextBytes(bytes);
|
||||
appRepository.save(new App(name, Base64Utils.encodeToString(bytes)));
|
||||
}
|
||||
|
||||
@Cacheable(value = APP_CACHE)
|
||||
@NotNull
|
||||
public List<App> getApps() {
|
||||
return appRepository.findAll();
|
||||
|
@ -90,19 +88,19 @@ public class DataManager {
|
|||
|
||||
@Nullable
|
||||
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) {
|
||||
appRepository.delete(id);
|
||||
appRepository.deleteById(id);
|
||||
getReportsForApp(id).forEach(this::deleteReport);
|
||||
mappingRepository.delete(getMappings(id));
|
||||
mappingRepository.deleteAll(getMappings(id));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<GridFSDBFile> getAttachments(@NotNull String report) {
|
||||
return gridFsTemplate.find(new Query(Criteria.where("metadata.reportId").is(report)));
|
||||
public List<Pair<GridFSFile, Supplier<GridFsResource>>> getAttachments(@NotNull String 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) {
|
||||
|
@ -111,7 +109,7 @@ public class DataManager {
|
|||
|
||||
@Nullable
|
||||
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
|
||||
|
@ -123,7 +121,6 @@ public class DataManager {
|
|||
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) {
|
||||
Report report = reportRepository.save(new Report(content, app));
|
||||
for (MultipartFile a : attachments) {
|
||||
|
@ -137,10 +134,10 @@ public class DataManager {
|
|||
notifyListeners(info);
|
||||
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.setLastReport(info.getDate());
|
||||
saveBug(bug);
|
||||
}
|
||||
|
||||
@Cacheable(APP_REPORT_CACHE)
|
||||
@NotNull
|
||||
public List<ReportInfo> getReportsForApp(@NotNull String 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) {
|
||||
return reportRepository.countByApp(app);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
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) {
|
||||
reportRepository.delete(report.getId());
|
||||
reportRepository.deleteById(report.getId());
|
||||
gridFsTemplate.delete(new Query(Criteria.where("metadata.reportId").is(report.getId())));
|
||||
bugRepository.findByReportIdsContains(report.getId()).forEach(bug -> {
|
||||
bug.getReportIds().remove(report.getId());
|
||||
|
@ -173,48 +173,54 @@ public class DataManager {
|
|||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@CacheEvict(value = APP_BUG_CACHE, key = "#a0.app")
|
||||
public void deleteBug(@NotNull Bug bug) {
|
||||
bugRepository.delete(bug);
|
||||
notifyListeners(bug);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@Caching(evict = {@CacheEvict(value = APP_BUG_CACHE, key = "#a0.app"), @CacheEvict(value = BUG_REPORT_CACHE, key = "#a0.id")})
|
||||
@NotNull
|
||||
public Bug saveBug(@NotNull Bug bug) {
|
||||
public void saveBug(@NotNull Bug bug) {
|
||||
Bug b = bugRepository.save(bug);
|
||||
notifyListeners(bug);
|
||||
return b;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Cacheable(value = APP_BUG_CACHE)
|
||||
public List<Bug> getBugs(@NotNull String app) {
|
||||
return bugRepository.findByApp(app);
|
||||
}
|
||||
|
||||
@Cacheable(value = BUG_REPORT_CACHE, key = "#a0.id")
|
||||
@NotNull
|
||||
public List<ReportInfo> getReportsForBug(@NotNull Bug bug) {
|
||||
try (Stream<Report> stream = reportRepository.streamAllByIdIn(bug.getReportIds())) {
|
||||
return stream.map(ReportInfo::new).collect(Collectors.toList());
|
||||
public DataProvider<Bug, Void> getLazyBugs(@NotNull String app, boolean includeSolved) {
|
||||
if (includeSolved) {
|
||||
return BufferedMongoDataProvider.of(pageable -> bugRepository.findAllByApp(app, pageable));
|
||||
} else {
|
||||
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) {
|
||||
return bug.getReportIds().size();
|
||||
}
|
||||
|
||||
public void rebuildBugs(@NotNull String app) {
|
||||
getBugs(app).forEach(this::deleteBug);
|
||||
List<Bug> bugs = new ArrayList<>();
|
||||
getReportsForApp(app).forEach(reportInfo -> bugs.stream().filter(bug -> matches(bug, reportInfo)).findAny().orElseGet(() -> {
|
||||
Bug bug = new Bug(reportInfo.getApp(), reportInfo.getStacktrace(), reportInfo.getVersionCode());
|
||||
bugs.add(bug);
|
||||
return bug;
|
||||
}).getReportIds().add(reportInfo.getId()));
|
||||
bugs.forEach(this::saveBug);
|
||||
bugRepository.deleteAll();
|
||||
Map<Bug, List<ReportInfo>> mapping = new HashMap<>();
|
||||
getReportsForApp(app).forEach(reportInfo -> mapping.entrySet().stream().filter(entry -> matches(entry.getKey(), reportInfo)).map(Map.Entry::getValue).findAny()
|
||||
.orElseGet(() -> {
|
||||
List<ReportInfo> list = new ArrayList<>();
|
||||
mapping.put(new Bug(reportInfo.getApp(), reportInfo.getStacktrace(), reportInfo.getVersionCode()), list);
|
||||
return list;
|
||||
})
|
||||
.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) {
|
||||
|
@ -275,5 +281,4 @@ public class DataManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package com.faendir.acra.mongod.data;
|
|||
|
||||
import com.faendir.acra.mongod.model.Report;
|
||||
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 java.util.stream.Stream;
|
||||
|
@ -14,10 +16,16 @@ interface ReportRepository extends MongoRepository<Report, String> {
|
|||
@NotNull
|
||||
Stream<Report> streamAllByApp(String app);
|
||||
|
||||
long countByApp(String app);
|
||||
int countByApp(String app);
|
||||
|
||||
@NotNull
|
||||
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);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package com.faendir.acra.mongod.model;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -14,25 +16,27 @@ import java.util.List;
|
|||
*/
|
||||
@Document
|
||||
public class Bug implements AppScoped {
|
||||
@NotNull private final String id;
|
||||
@Nullable private final String id;
|
||||
@NotNull @Indexed private final String app;
|
||||
@NotNull private final String stacktrace;
|
||||
private final int versionCode;
|
||||
@NotNull private final List<String> reportIds;
|
||||
private boolean solved;
|
||||
@Nullable private Date lastReport;
|
||||
|
||||
@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.app = app;
|
||||
this.solved = solved;
|
||||
this.stacktrace = stacktrace;
|
||||
this.versionCode = versionCode;
|
||||
this.reportIds = reportIds;
|
||||
this.lastReport = lastReport;
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -63,8 +67,19 @@ public class Bug implements AppScoped {
|
|||
return reportIds;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Nullable
|
||||
public String getId() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,18 +48,16 @@ public class UserManager {
|
|||
|
||||
@Nullable
|
||||
public User getUser(@NotNull String username) {
|
||||
User user = userRepository.findOne(username);
|
||||
if (user == null && defaultUser.equals(username)) {
|
||||
user = getDefaultUser();
|
||||
Optional<User> user = userRepository.findById(username);
|
||||
if (!user.isPresent() && defaultUser.equals(username)) {
|
||||
user = Optional.of(getDefaultUser());
|
||||
}
|
||||
if (user != null) {
|
||||
ensureValidPermissions(user);
|
||||
}
|
||||
return user;
|
||||
user.ifPresent(this::ensureValidPermissions);
|
||||
return user.orElse(null);
|
||||
}
|
||||
|
||||
public void createUser(@NotNull String username, @NotNull String password) {
|
||||
if (userRepository.exists(username)) {
|
||||
if (userRepository.existsById(username)) {
|
||||
throw new IllegalArgumentException("Username already exists");
|
||||
}
|
||||
User user = new User(username, passwordEncoder.encode(password), Collections.singleton(ROLE_USER));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -55,6 +55,7 @@ public class BackendUI extends UI {
|
|||
} else {
|
||||
showLogin();
|
||||
}
|
||||
//applicationContext.getBean(ToSqlMigrator.class).migrate();
|
||||
}
|
||||
|
||||
private void login(@NotNull String username, @NotNull String password) {
|
||||
|
|
|
@ -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.base.NamedView;
|
||||
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.server.FileDownloader;
|
||||
import com.vaadin.server.StreamResource;
|
||||
|
@ -23,12 +23,16 @@ import com.vaadin.ui.VerticalLayout;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
|
@ -81,10 +85,16 @@ public class ReportView extends NamedView {
|
|||
public void enter(@NotNull ViewChangeListener.ViewChangeEvent event) {
|
||||
Report report = dataManager.getReport(event.getParameters());
|
||||
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 -> {
|
||||
Button button = new Button(file.getFilename());
|
||||
new FileDownloader(new StreamResource(file::getInputStream, file.getFilename())).extend(button);
|
||||
Button button = new Button(file.getFirst().getFilename());
|
||||
new FileDownloader(new StreamResource(() -> {
|
||||
try {
|
||||
return file.getSecond().get().getInputStream();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}, file.getFirst().getFilename())).extend(button);
|
||||
return button;
|
||||
}).toArray(Component[]::new));
|
||||
Style.apply(attachments, Style.MARGIN_BOTTOM, Style.MARGIN_TOP, Style.MARGIN_LEFT, Style.MARGIN_RIGHT);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.faendir.acra.ui.view.base;
|
||||
|
||||
import com.vaadin.data.ValueProvider;
|
||||
import com.vaadin.data.provider.DataProvider;
|
||||
import com.vaadin.ui.Grid;
|
||||
import com.vaadin.ui.renderers.AbstractRenderer;
|
||||
import com.vaadin.ui.renderers.TextRenderer;
|
||||
|
@ -14,6 +15,10 @@ import java.util.Collection;
|
|||
* @since 14.05.2017
|
||||
*/
|
||||
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) {
|
||||
super(caption, items);
|
||||
|
@ -27,7 +32,18 @@ public class MyGrid<T> extends Grid<T> {
|
|||
|
||||
@NotNull
|
||||
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
|
||||
|
|
|
@ -7,13 +7,12 @@ import com.faendir.acra.security.SecurityUtils;
|
|||
import com.faendir.acra.ui.NavigationManager;
|
||||
import com.faendir.acra.ui.view.ReportView;
|
||||
import com.faendir.acra.util.TimeSpanRenderer;
|
||||
import com.vaadin.data.provider.DataProvider;
|
||||
import com.vaadin.shared.data.sort.SortDirection;
|
||||
import com.vaadin.ui.renderers.ButtonRenderer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
|
@ -21,22 +20,22 @@ import java.util.function.Supplier;
|
|||
*/
|
||||
public class ReportList extends MyGrid<ReportInfo> implements DataManager.Listener<ReportInfo> {
|
||||
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;
|
||||
|
||||
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) {
|
||||
super(CAPTION, reportSupplier.get());
|
||||
super(CAPTION, reportSupplier);
|
||||
setId(CAPTION);
|
||||
this.reportSupplier = reportSupplier;
|
||||
this.relevanceFunction = relevanceFunction;
|
||||
setWidth(100, Unit.PERCENTAGE);
|
||||
setSelectionMode(SelectionMode.NONE);
|
||||
sort(addColumn(ReportInfo::getDate, new TimeSpanRenderer(), "Date"), SortDirection.DESCENDING);
|
||||
addColumn(ReportInfo::getVersionCode, "App Version");
|
||||
addColumn(ReportInfo::getAndroidVersion, "Android Version");
|
||||
addColumn(ReportInfo::getPhoneModel, "Device");
|
||||
addColumn(report -> report.getStacktrace().split("\n", 2)[0], "Stacktrace").setExpandRatio(1);
|
||||
sort(addColumn(ReportInfo::getDate, new TimeSpanRenderer(), "content.map.USER_CRASH_DATE", "Date"), SortDirection.DESCENDING);
|
||||
addColumn(ReportInfo::getVersionCode, "content.map.APP_VERSION_CODE","App Version");
|
||||
addColumn(ReportInfo::getAndroidVersion, "content.map.ANDROID_VERSION","Android Version");
|
||||
addColumn(ReportInfo::getPhoneModel, "content.map.PHONE_MODEL","Device");
|
||||
addColumn(report -> report.getStacktrace().split("\n", 2)[0], "content.map.STACK_TRACE","Stacktrace").setExpandRatio(1);
|
||||
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
|
||||
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() {
|
||||
getUI().access(() -> setItems(reportSupplier.get()));
|
||||
getUI().access(reportSupplier::refreshAll);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.faendir.acra.ui.view.tabs;
|
||||
|
||||
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.Bug;
|
||||
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.CheckBox;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
import com.vaadin.ui.renderers.ComponentRenderer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
|
@ -46,15 +44,15 @@ public class BugTab extends VerticalLayout implements DataManager.Listener<AppSc
|
|||
hideSolved.addValueChangeListener(e -> setItems());
|
||||
addComponent(hideSolved);
|
||||
setComponentAlignment(hideSolved, Alignment.MIDDLE_RIGHT);
|
||||
bugs = new MyGrid<>(null, getBugs());
|
||||
bugs = new MyGrid<>(null, dataManager.getLazyBugs(app, false));
|
||||
bugs.setWidth(100, Unit.PERCENTAGE);
|
||||
bugs.addColumn(dataManager::reportCountForBug, "Reports");
|
||||
bugs.sort(bugs.addColumn(bug -> ReportUtils.getLastReportDate(dataManager.getReportsForBug(bug)), new TimeSpanRenderer(), "Latest Report"), SortDirection.DESCENDING);
|
||||
bugs.addColumn(Bug::getVersionCode, "Version");
|
||||
bugs.addColumn(bug -> bug.getStacktrace().split("\n", 2)[0], "Stacktrace").setExpandRatio(1);
|
||||
bugs.addColumn(bug -> bug.getReportIds().size(), "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);
|
||||
bugs.addSelectionListener(this::handleBugSelection);
|
||||
bugs.addComponentColumn(bug -> new MyCheckBox(bug.isSolved(), SecurityUtils.hasPermission(app, Permission.Level.EDIT), e -> dataManager.setBugSolved(bug, e.getValue())))
|
||||
.setCaption("Solved");
|
||||
bugs.addColumn(bug -> new MyCheckBox(bug.isSolved(), SecurityUtils.hasPermission(app, Permission.Level.EDIT), e -> dataManager.setBugSolved(bug, e.getValue())),
|
||||
new ComponentRenderer(), "Solved");
|
||||
addComponent(bugs);
|
||||
Style.NO_PADDING.apply(this);
|
||||
setCaption(CAPTION);
|
||||
|
@ -66,7 +64,7 @@ public class BugTab extends VerticalLayout implements DataManager.Listener<AppSc
|
|||
Optional<Bug> selection = e.getFirstSelectedItem();
|
||||
ReportList reportList = null;
|
||||
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));
|
||||
replaceComponent(this.reportList, reportList);
|
||||
} else if (this.reportList != null) {
|
||||
|
@ -85,17 +83,8 @@ public class BugTab extends VerticalLayout implements DataManager.Listener<AppSc
|
|||
private void setItems() {
|
||||
getUI().access(() -> {
|
||||
Set<Bug> selection = bugs.getSelectedItems();
|
||||
bugs.setItems(getBugs());
|
||||
bugs.setDataProvider(dataManager.getLazyBugs(app, !hideSolved.getValue()));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@ import org.jetbrains.annotations.NotNull;
|
|||
*/
|
||||
public class ReportTab extends ReportList {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,6 @@ import java.util.Date;
|
|||
* @since 26.05.2017
|
||||
*/
|
||||
public class TimeSpanRenderer extends TextRenderer {
|
||||
public TimeSpanRenderer() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonValue encode(@Nullable Object value) {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<inherits name="org.vaadin.hene.popupbutton.widgetset.PopupbuttonWidgetset" />
|
||||
<inherits name="org.vaadin.risto.stepper.StepperWidgetset" />
|
||||
<set-configuration-property name="devModeRedirectEnabled" value="true" />
|
||||
<set-property name="user.agent" value="ie8,ie9,gecko1_8,safari,ie10" />
|
||||
<set-property name="user.agent" value="gecko1_8,safari,ie10" />
|
||||
<source path="client" />
|
||||
<source path="shared" />
|
||||
<collapse-all-properties />
|
||||
|
|
Loading…
Reference in a new issue