From 694d48b352af9cac15b46e11a7503aae88619237 Mon Sep 17 00:00:00 2001 From: F43nd1r Date: Tue, 12 Dec 2017 01:13:48 +0100 Subject: [PATCH] mongod pre-delete --- .../acra/mongod/MongoConfiguration.java | 4 +- .../acra/mongod/data/BugRepository.java | 15 +++- .../faendir/acra/mongod/data/DataManager.java | 89 ++++++++++--------- .../acra/mongod/data/ReportRepository.java | 10 ++- .../com/faendir/acra/mongod/model/Bug.java | 23 ++++- .../faendir/acra/mongod/user/UserManager.java | 14 ++- .../util/BufferedMongoDataProvider.java | 64 +++++++++++++ .../java/com/faendir/acra/ui/BackendUI.java | 1 + .../com/faendir/acra/ui/view/ReportView.java | 18 +++- .../com/faendir/acra/ui/view/base/MyGrid.java | 18 +++- .../faendir/acra/ui/view/base/ReportList.java | 21 +++-- .../com/faendir/acra/ui/view/tabs/BugTab.java | 31 +++---- .../faendir/acra/ui/view/tabs/ReportTab.java | 2 +- .../faendir/acra/util/TimeSpanRenderer.java | 2 - .../src/main/resources/AppWidgetset.gwt.xml | 2 +- 15 files changed, 212 insertions(+), 102 deletions(-) create mode 100644 backend/src/main/java/com/faendir/acra/mongod/util/BufferedMongoDataProvider.java diff --git a/backend/src/main/java/com/faendir/acra/mongod/MongoConfiguration.java b/backend/src/main/java/com/faendir/acra/mongod/MongoConfiguration.java index e7cb589..57dcf8e 100644 --- a/backend/src/main/java/com/faendir/acra/mongod/MongoConfiguration.java +++ b/backend/src/main/java/com/faendir/acra/mongod/MongoConfiguration.java @@ -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; } diff --git a/backend/src/main/java/com/faendir/acra/mongod/data/BugRepository.java b/backend/src/main/java/com/faendir/acra/mongod/data/BugRepository.java index 6550b60..f8d880d 100644 --- a/backend/src/main/java/com/faendir/acra/mongod/data/BugRepository.java +++ b/backend/src/main/java/com/faendir/acra/mongod/data/BugRepository.java @@ -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 { - @NotNull - List findByApp(String app); - @NotNull - List findByReportIdsContains(String reportId); + @NotNull List findByApp(String app); + + @NotNull Page findAllByApp(String app, Pageable pageable); + + @NotNull Page findAllByAppAndSolvedIsFalse(String app, Pageable pageable); + + int countAllByApp(String app); + + @NotNull List findByReportIdsContains(String reportId); } diff --git a/backend/src/main/java/com/faendir/acra/mongod/data/DataManager.java b/backend/src/main/java/com/faendir/acra/mongod/data/DataManager.java index 14cfeea..94f440d 100644 --- a/backend/src/main/java/com/faendir/acra/mongod/data/DataManager.java +++ b/backend/src/main/java/com/faendir/acra/mongod/data/DataManager.java @@ -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 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 getAttachments(@NotNull String report) { - return gridFsTemplate.find(new Query(Criteria.where("metadata.reportId").is(report))); + public List>> getAttachments(@NotNull String report) { + return gridFsTemplate.find(new Query(Criteria.where("metadata.reportId").is(report))) + .map(file -> Pair.of(file, (Supplier) () -> 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 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 getReportsForApp(@NotNull String app) { try (Stream stream = reportRepository.streamAllByApp(app)) { @@ -148,18 +145,21 @@ public class DataManager { } } + public DataProvider 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 getBugs(@NotNull String app) { return bugRepository.findByApp(app); } - @Cacheable(value = BUG_REPORT_CACHE, key = "#a0.id") - @NotNull - public List getReportsForBug(@NotNull Bug bug) { - try (Stream stream = reportRepository.streamAllByIdIn(bug.getReportIds())) { - return stream.map(ReportInfo::new).collect(Collectors.toList()); + public DataProvider 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 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 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> mapping = new HashMap<>(); + getReportsForApp(app).forEach(reportInfo -> mapping.entrySet().stream().filter(entry -> matches(entry.getKey(), reportInfo)).map(Map.Entry::getValue).findAny() + .orElseGet(() -> { + List 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 { } } } - } diff --git a/backend/src/main/java/com/faendir/acra/mongod/data/ReportRepository.java b/backend/src/main/java/com/faendir/acra/mongod/data/ReportRepository.java index 88f06b2..92a9fc3 100644 --- a/backend/src/main/java/com/faendir/acra/mongod/data/ReportRepository.java +++ b/backend/src/main/java/com/faendir/acra/mongod/data/ReportRepository.java @@ -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 { @NotNull Stream streamAllByApp(String app); - long countByApp(String app); + int countByApp(String app); @NotNull Stream streamAllByIdIn(@NotNull Iterable ids); + @NotNull + Page findAllByApp(String app, Pageable pageable); + + @NotNull + Page findAllByIdIn(@NotNull Iterable ids, Pageable pageable); + } diff --git a/backend/src/main/java/com/faendir/acra/mongod/model/Bug.java b/backend/src/main/java/com/faendir/acra/mongod/model/Bug.java index c0f92fd..b87a667 100644 --- a/backend/src/main/java/com/faendir/acra/mongod/model/Bug.java +++ b/backend/src/main/java/com/faendir/acra/mongod/model/Bug.java @@ -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 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 reportIds) { + private Bug(@Nullable String id, @NotNull String app, boolean solved, @NotNull String stacktrace, int versionCode, @NotNull List 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; + } + } } diff --git a/backend/src/main/java/com/faendir/acra/mongod/user/UserManager.java b/backend/src/main/java/com/faendir/acra/mongod/user/UserManager.java index 17e4dfa..7191862 100644 --- a/backend/src/main/java/com/faendir/acra/mongod/user/UserManager.java +++ b/backend/src/main/java/com/faendir/acra/mongod/user/UserManager.java @@ -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 = 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)); diff --git a/backend/src/main/java/com/faendir/acra/mongod/util/BufferedMongoDataProvider.java b/backend/src/main/java/com/faendir/acra/mongod/util/BufferedMongoDataProvider.java new file mode 100644 index 0000000..7e39eef --- /dev/null +++ b/backend/src/main/java/com/faendir/acra/mongod/util/BufferedMongoDataProvider.java @@ -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 extends AbstractBackEndDataProvider { + private static final int PAGE_SIZE = 32; + private final Function> getter; + private final Function transformer; + + public static BufferedMongoDataProvider of(Function> getter, Function transformer) { + return new BufferedMongoDataProvider<>(getter, transformer); + } + + public static BufferedMongoDataProvider of(Function> getter){ + return of(getter, Function.identity()); + } + + private BufferedMongoDataProvider(Function> getter, Function transformer){ + this.getter = getter; + this.transformer = transformer; + } + + @Override + protected Stream fetchFromBackEnd(Query 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 page = getter.apply(PageRequest.of(query.getOffset() / PAGE_SIZE, PAGE_SIZE, sort)); + if (!page.hasContent()) return Stream.empty(); + List content = page.getContent(); + int ignore = query.getOffset() % PAGE_SIZE; + int size = content.size() - ignore; + Stream 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 query) { + return (int) getter.apply(PageRequest.of(0, 1)).getTotalElements(); + } +} diff --git a/backend/src/main/java/com/faendir/acra/ui/BackendUI.java b/backend/src/main/java/com/faendir/acra/ui/BackendUI.java index eea505b..71a47ea 100644 --- a/backend/src/main/java/com/faendir/acra/ui/BackendUI.java +++ b/backend/src/main/java/com/faendir/acra/ui/BackendUI.java @@ -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) { diff --git a/backend/src/main/java/com/faendir/acra/ui/view/ReportView.java b/backend/src/main/java/com/faendir/acra/ui/view/ReportView.java index 89ed96c..ec71921 100644 --- a/backend/src/main/java/com/faendir/acra/ui/view/ReportView.java +++ b/backend/src/main/java/com/faendir/acra/ui/view/ReportView.java @@ -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 attachmentList = dataManager.getAttachments(report.getId()); + List>> 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); diff --git a/backend/src/main/java/com/faendir/acra/ui/view/base/MyGrid.java b/backend/src/main/java/com/faendir/acra/ui/view/base/MyGrid.java index 42ac423..fdae69a 100644 --- a/backend/src/main/java/com/faendir/acra/ui/view/base/MyGrid.java +++ b/backend/src/main/java/com/faendir/acra/ui/view/base/MyGrid.java @@ -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 extends Grid { + public MyGrid(String caption, DataProvider dataProvider) { + super(caption, dataProvider); + setSizeFull(); + } public MyGrid(@Nullable String caption, @NotNull Collection items) { super(caption, items); @@ -27,7 +32,18 @@ public class MyGrid extends Grid { @NotNull public Grid.Column addColumn(@NotNull ValueProvider valueProvider, @NotNull AbstractRenderer renderer, @NotNull String caption) { - return addColumn(valueProvider, renderer).setId(caption).setCaption(caption); + return addColumn(valueProvider, renderer).setId(caption).setCaption(caption).setSortable(false); + } + + @NotNull + public Grid.Column addColumn(@NotNull ValueProvider valueProvider, @NotNull String id, @NotNull String caption) { + return addColumn(valueProvider, new TextRenderer(), id, caption); + } + + @NotNull + public Grid.Column addColumn(@NotNull ValueProvider valueProvider, @NotNull AbstractRenderer renderer, @NotNull String id, + @NotNull String caption) { + return addColumn(valueProvider, renderer).setId(id).setCaption(caption); } @Override diff --git a/backend/src/main/java/com/faendir/acra/ui/view/base/ReportList.java b/backend/src/main/java/com/faendir/acra/ui/view/base/ReportList.java index ecc6220..4dc605a 100644 --- a/backend/src/main/java/com/faendir/acra/ui/view/base/ReportList.java +++ b/backend/src/main/java/com/faendir/acra/ui/view/base/ReportList.java @@ -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 implements DataManager.Listener { public static final String CAPTION = "Reports"; - @NotNull private final Supplier> reportSupplier; + @NotNull private final DataProvider reportSupplier; @NotNull private final Function relevanceFunction; - public ReportList(String app, @NotNull NavigationManager navigationManager, @NotNull DataManager dataManager, @NotNull Supplier> reportSupplier, + public ReportList(String app, @NotNull NavigationManager navigationManager, @NotNull DataManager dataManager, @NotNull DataProvider reportSupplier, @NotNull Function 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 implements DataManager.Listen } private void setItems() { - getUI().access(() -> setItems(reportSupplier.get())); + getUI().access(reportSupplier::refreshAll); } } diff --git a/backend/src/main/java/com/faendir/acra/ui/view/tabs/BugTab.java b/backend/src/main/java/com/faendir/acra/ui/view/tabs/BugTab.java index 58f4632..f652a29 100644 --- a/backend/src/main/java/com/faendir/acra/ui/view/tabs/BugTab.java +++ b/backend/src/main/java/com/faendir/acra/ui/view/tabs/BugTab.java @@ -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 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 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 { Set selection = bugs.getSelectedItems(); - bugs.setItems(getBugs()); + bugs.setDataProvider(dataManager.getLazyBugs(app, !hideSolved.getValue())); selection.forEach(bugs::select); }); } - - @NotNull - private List getBugs() { - List bugs = dataManager.getBugs(app); - if (hideSolved.getValue()) { - return bugs.stream().filter(bug -> !bug.isSolved()).collect(Collectors.toList()); - } - return bugs; - } } diff --git a/backend/src/main/java/com/faendir/acra/ui/view/tabs/ReportTab.java b/backend/src/main/java/com/faendir/acra/ui/view/tabs/ReportTab.java index ce24175..5bfd28e 100644 --- a/backend/src/main/java/com/faendir/acra/ui/view/tabs/ReportTab.java +++ b/backend/src/main/java/com/faendir/acra/ui/view/tabs/ReportTab.java @@ -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)); } } diff --git a/backend/src/main/java/com/faendir/acra/util/TimeSpanRenderer.java b/backend/src/main/java/com/faendir/acra/util/TimeSpanRenderer.java index 8ae0e04..67b0278 100644 --- a/backend/src/main/java/com/faendir/acra/util/TimeSpanRenderer.java +++ b/backend/src/main/java/com/faendir/acra/util/TimeSpanRenderer.java @@ -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) { diff --git a/backend/src/main/resources/AppWidgetset.gwt.xml b/backend/src/main/resources/AppWidgetset.gwt.xml index bb39857..462c76c 100644 --- a/backend/src/main/resources/AppWidgetset.gwt.xml +++ b/backend/src/main/resources/AppWidgetset.gwt.xml @@ -6,7 +6,7 @@ - +