From 52d001c26eff536811bbd87e9daab9767e1e87d1 Mon Sep 17 00:00:00 2001 From: Keir Nellyer Date: Wed, 26 Oct 2016 10:28:01 +0100 Subject: [PATCH] PC-1153 Add /reportmetrics command --- .../mineplex/core/report/ReportManager.java | 32 +++- .../mineplex/core/report/ReportPlugin.java | 2 + .../core/report/ReportResultType.java | 14 +- .../report/command/ReportMetricsCommand.java | 108 ++++++++++++ .../data/metrics/ReportGlobalMetrics.java | 37 +++++ .../report/data/metrics/ReportMetrics.java | 48 ++++++ .../data/metrics/ReportMetricsRepository.java | 156 ++++++++++++++++++ 7 files changed, 385 insertions(+), 12 deletions(-) create mode 100644 Plugins/Mineplex.Core/src/mineplex/core/report/command/ReportMetricsCommand.java create mode 100644 Plugins/Mineplex.Core/src/mineplex/core/report/data/metrics/ReportGlobalMetrics.java create mode 100644 Plugins/Mineplex.Core/src/mineplex/core/report/data/metrics/ReportMetrics.java create mode 100644 Plugins/Mineplex.Core/src/mineplex/core/report/data/metrics/ReportMetricsRepository.java diff --git a/Plugins/Mineplex.Core/src/mineplex/core/report/ReportManager.java b/Plugins/Mineplex.Core/src/mineplex/core/report/ReportManager.java index 076b362b6..428645d21 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/report/ReportManager.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/report/ReportManager.java @@ -34,6 +34,7 @@ import mineplex.core.portal.Portal; import mineplex.core.punish.Category; import mineplex.core.punish.Punish; import mineplex.core.punish.PunishClient; +import mineplex.core.report.data.metrics.ReportMetricsRepository; import mineplex.core.report.redis.HandlerNotification; import mineplex.core.report.data.Report; import mineplex.core.report.data.ReportMessage; @@ -64,7 +65,8 @@ public class ReportManager private final int _serverWeight; private final ReportRepository _reportRepository; - private final ReportUserRepository _reportUserRepository; + private final ReportUserRepository _userRepository; + private final ReportMetricsRepository _metricsRepository; public ReportManager(JavaPlugin plugin, SnapshotManager snapshotManager, CoreClientManager clientManager, IncognitoManager incognitoManager, Punish punish, Region region, String serverName, int serverWeight) @@ -79,8 +81,8 @@ public class ReportManager _serverWeight = serverWeight; _reportRepository = new ReportRepository(this, _plugin.getLogger()); - - _reportUserRepository = new ReportUserRepository(plugin); + _userRepository = new ReportUserRepository(plugin); + _metricsRepository = new ReportMetricsRepository(_plugin.getLogger()); ServerCommandManager commandManager = ServerCommandManager.getInstance(); ReportRedisManager notificationCallback = new ReportRedisManager(this, _serverName); @@ -101,7 +103,7 @@ public class ReportManager } /** - * Gets the {@link ReportRepository} we are using. + * Gets the {@link ReportRepository} this instance is using. * * @return the repository */ @@ -110,6 +112,26 @@ public class ReportManager return _reportRepository; } + /** + * Gets the {@link ReportUserRepository} this instance is using. + * + * @return the repository + */ + public ReportUserRepository getUserRepository() + { + return _userRepository; + } + + /** + * Gets the {@link ReportMetricsRepository} this instance is using. + * + * @return the repository + */ + public ReportMetricsRepository getMetricsRepository() + { + return _metricsRepository; + } + /** * Creates a new report or adds to an existing one. * @@ -595,7 +617,7 @@ public class ReportManager for (Map.Entry entry : report.getMessages().entrySet()) { int accountId = entry.getKey(); - ReportUser user = _reportUserRepository.getUser(accountId).join(); + ReportUser user = _userRepository.getUser(accountId).join(); int categoryReputation = user.getReputation(report.getCategory()); ReportMessage message = entry.getValue(); diff --git a/Plugins/Mineplex.Core/src/mineplex/core/report/ReportPlugin.java b/Plugins/Mineplex.Core/src/mineplex/core/report/ReportPlugin.java index 15a364d82..47c8a3c81 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/report/ReportPlugin.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/report/ReportPlugin.java @@ -11,6 +11,7 @@ import mineplex.core.report.command.ReportCommand; import mineplex.core.report.command.ReportHandleCommand; import mineplex.core.report.command.ReportInfoCommand; import mineplex.core.report.command.ReportHistoryCommand; +import mineplex.core.report.command.ReportMetricsCommand; /** * Main class for this module, handles initialization and disabling of the module. @@ -38,6 +39,7 @@ public class ReportPlugin extends MiniPlugin addCommand(new ReportCloseCommand(this)); addCommand(new ReportHistoryCommand(this)); addCommand(new ReportInfoCommand(this)); + addCommand(new ReportMetricsCommand(this)); } @EventHandler diff --git a/Plugins/Mineplex.Core/src/mineplex/core/report/ReportResultType.java b/Plugins/Mineplex.Core/src/mineplex/core/report/ReportResultType.java index af2cb3e8b..e54b5e780 100644 --- a/Plugins/Mineplex.Core/src/mineplex/core/report/ReportResultType.java +++ b/Plugins/Mineplex.Core/src/mineplex/core/report/ReportResultType.java @@ -7,23 +7,23 @@ import org.apache.commons.lang3.text.WordUtils; */ public enum ReportResultType { - ACCEPTED(0, false), - DENIED(1, false), - ABUSIVE(2, true), - EXPIRED(3, true); + ACCEPTED((short) 0, false), + DENIED((short) 1, false), + ABUSIVE((short) 2, true), + EXPIRED((short) 3, true); - private final int _id; + private final short _id; private final boolean _globalStat; private final String _name; - ReportResultType(int id, boolean globalStat) + ReportResultType(short id, boolean globalStat) { _id = id; _globalStat = globalStat; _name = WordUtils.capitalizeFully(name().replace('_', ' ')); } - public int getId() + public short getId() { return _id; } diff --git a/Plugins/Mineplex.Core/src/mineplex/core/report/command/ReportMetricsCommand.java b/Plugins/Mineplex.Core/src/mineplex/core/report/command/ReportMetricsCommand.java new file mode 100644 index 000000000..239df4fce --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/report/command/ReportMetricsCommand.java @@ -0,0 +1,108 @@ +package mineplex.core.report.command; + +import org.bukkit.entity.Player; + +import mineplex.core.command.CommandBase; +import mineplex.core.common.Rank; +import mineplex.core.common.util.BukkitFuture; +import mineplex.core.common.util.C; +import mineplex.core.common.util.F; +import mineplex.core.common.util.UtilPlayer; +import mineplex.core.report.ReportPlugin; +import mineplex.core.report.data.metrics.ReportMetrics; + +/** + * Displays various report-related metrics on a player. + */ +public class ReportMetricsCommand extends CommandBase +{ + public ReportMetricsCommand(ReportPlugin plugin) + { + super(plugin, Rank.MODERATOR, "reportmetrics"); + } + + @Override + public void Execute(Player player, String[] args) + { + if (args.length <= 2) + { + int days = 30; + + if (args.length >= 1) // has target argument + { + String targetName = args[0]; + + if (args.length > 1) + { + String daysString = args[1]; + + try + { + days = Integer.parseInt(daysString); + } + catch (NumberFormatException e) + { + UtilPlayer.message(player, F.main(Plugin.getName(), F.elem(daysString) + C.cRed + " is not a valid number.")); + } + } + + int finalDays = days; + Plugin.getReportManager().getRepository().getAccountId(targetName).thenAccept(targetIdOptional -> + { + if (targetIdOptional.isPresent()) + { + int targetId = targetIdOptional.get(); + displayUserMetrics(player, targetName, targetId, finalDays); + } + else + { + UtilPlayer.message(player, F.main(Plugin.getName(), C.cRed + "Player not found.")); + } + }); + } + else // display global metrics + { + // TODO: do we parse days for global metrics? + UtilPlayer.message(player, F.main("Report Metrics", F.elem("Global Metrics") + " (" + F.elem(days + " days") + ")")); + displayGlobalMetrics(player, days); + } + } + else + { + UtilPlayer.message(player, + F.main(Plugin.getName(), C.cRed + "Invalid Usage: " + + F.elem("/" + _aliasUsed + " [days]"))); + } + } + + public void displayGlobalMetrics(Player player, int days) + { + Plugin.getReportManager().getMetricsRepository().getGlobalMetrics(days).thenCompose( + BukkitFuture.accept(globalMetrics -> + { + UtilPlayer.message(player, F.main("Report Metrics", "Submitted: " + F.elem(globalMetrics.getSubmitted()))); + UtilPlayer.message(player, F.main("Report Metrics", "Expired: " + F.elem(globalMetrics.getExpired()))); + displayMetrics(player, globalMetrics); + })); + } + + public void displayUserMetrics(Player player, String targetName, int targetId, int days) + { + Plugin.getReportManager().getMetricsRepository().getUserMetrics(targetId, days).thenCompose( + BukkitFuture.accept(userMetrics -> + { + UtilPlayer.message(player, + F.main("Report Metrics", + F.elem(targetName) + " (" + F.elem(days + " days") + ")")); + + displayMetrics(player, userMetrics); + })); + } + + public void displayMetrics(Player player, ReportMetrics reportMetrics) + { + UtilPlayer.message(player, F.main("Report Metrics", "Accepted: " + F.elem(reportMetrics.getAccepted()))); + UtilPlayer.message(player, F.main("Report Metrics", "Denied: " + F.elem(reportMetrics.getDenied()))); + UtilPlayer.message(player, F.main("Report Metrics", "Flagged Abusive: " + F.elem(reportMetrics.getFlagged()))); + } +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/report/data/metrics/ReportGlobalMetrics.java b/Plugins/Mineplex.Core/src/mineplex/core/report/data/metrics/ReportGlobalMetrics.java new file mode 100644 index 000000000..c81056a83 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/report/data/metrics/ReportGlobalMetrics.java @@ -0,0 +1,37 @@ +package mineplex.core.report.data.metrics; + +/** + * Extends the standard report metrics class to hold global-scope metrics. + */ +public class ReportGlobalMetrics extends ReportMetrics +{ + private final long _submitted; + private final long _expired; + + public ReportGlobalMetrics(long submitted, long expired, long accepted, long denied, long flagged) + { + super(accepted, denied, flagged); + _submitted = submitted; + _expired = expired; + } + + /** + * Gets the amount of reports submitted. + * + * @return the amount + */ + public long getSubmitted() + { + return _submitted; + } + + /** + * Gets the amount of reports expired. + * + * @return the amount + */ + public long getExpired() + { + return _expired; + } +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/report/data/metrics/ReportMetrics.java b/Plugins/Mineplex.Core/src/mineplex/core/report/data/metrics/ReportMetrics.java new file mode 100644 index 000000000..16314b2a1 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/report/data/metrics/ReportMetrics.java @@ -0,0 +1,48 @@ +package mineplex.core.report.data.metrics; + +/** + * Holds report-related metrics which can be applied to a user or global scope. + */ +public class ReportMetrics +{ + private final long _accepted; + private final long _denied; + private final long _flagged; + + public ReportMetrics(long accepted, long denied, long flagged) + { + _accepted = accepted; + _denied = denied; + _flagged = flagged; + } + + /** + * Gets the amount of reports accepted. + * + * @return the amount + */ + public long getAccepted() + { + return _accepted; + } + + /** + * Gets the amount of reports denied. + * + * @return the amount + */ + public long getDenied() + { + return _denied; + } + + /** + * Gets the amount of reports flagged (marked as spam). + * + * @return the amount + */ + public long getFlagged() + { + return _flagged; + } +} diff --git a/Plugins/Mineplex.Core/src/mineplex/core/report/data/metrics/ReportMetricsRepository.java b/Plugins/Mineplex.Core/src/mineplex/core/report/data/metrics/ReportMetricsRepository.java new file mode 100644 index 000000000..884f1a378 --- /dev/null +++ b/Plugins/Mineplex.Core/src/mineplex/core/report/data/metrics/ReportMetricsRepository.java @@ -0,0 +1,156 @@ +package mineplex.core.report.data.metrics; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.logging.Logger; + +import mineplex.core.report.ReportResultType; +import mineplex.serverdata.database.DBPool; + +/** + * Handles all fetching of report-related metrics. + */ +public class ReportMetricsRepository +{ + private static final String GET_GLOBAL_SUBMITTED = "SELECT COUNT(DISTINCT reportReasons.reportId) AS submitted FROM reportReasons\n" + + " WHERE reportReasons.time BETWEEN NOW() - INTERVAL ? DAY AND NOW();"; + + private static final String GET_GLOBAL_RESULT = "SELECT COUNT(reportResults.reportId) AS amount FROM reportResults\n" + + " WHERE reportResults.resultId = ?\n" + + " AND reportResults.closedTime BETWEEN NOW() - INTERVAL ? DAY AND NOW();"; + + private static final String GET_USER_RESULT = "SELECT COUNT(reportResults.reportId) AS amount FROM reportResults\n" + + " LEFT JOIN reportHandlers ON reportResults.reportId = reportHandlers.reportId AND reportHandlers.aborted IS FALSE\n" + + " WHERE reportHandlers.handlerId = ?\n" + + " AND reportResults.resultId = ?\n" + + " AND reportResults.closedTime BETWEEN NOW() - INTERVAL ? DAY AND NOW();"; + + private final Logger _logger; + + public ReportMetricsRepository(Logger logger) + { + _logger = logger; + } + + public CompletableFuture getGlobalMetrics(int days) + { + CompletableFuture future = CompletableFuture.supplyAsync(() -> + { + try (Connection connection = DBPool.getAccount().getConnection()) + { + long submitted = getGlobalSubmitted(connection, days); + + try (PreparedStatement preparedStatement = connection.prepareStatement(GET_GLOBAL_RESULT)) + { + long expired = getGlobalResult(preparedStatement, ReportResultType.EXPIRED, days); + long accepted = getGlobalResult(preparedStatement, ReportResultType.ACCEPTED, days); + long denied = getGlobalResult(preparedStatement, ReportResultType.DENIED, days); + long flagged = getGlobalResult(preparedStatement, ReportResultType.ABUSIVE, days); + + return new ReportGlobalMetrics(submitted, expired, accepted, denied, flagged); + } + } + catch (SQLException e) + { + throw new RuntimeException(e); + } + }); + + future.exceptionally(throwable -> + { + _logger.log(Level.SEVERE, "Error fetching global metrics.", throwable); + return null; + }); + + return future; + } + + private long getGlobalSubmitted(Connection connection, int days) throws SQLException + { + PreparedStatement preparedStatement = connection.prepareStatement(GET_GLOBAL_SUBMITTED); + preparedStatement.setInt(1, days); + + try (ResultSet resultSet = preparedStatement.executeQuery()) + { + if (resultSet.next()) + { + return resultSet.getLong("submitted"); + } + else + { + return 0; + } + } + } + + private long getGlobalResult(PreparedStatement preparedStatement, ReportResultType resultType, int days) throws SQLException + { + preparedStatement.setShort(1, resultType.getId()); + preparedStatement.setInt(2, days); + + try (ResultSet resultSet = preparedStatement.executeQuery()) + { + if (resultSet.next()) + { + return resultSet.getLong("amount"); + } + else + { + return 0; + } + } + } + + public CompletableFuture getUserMetrics(int accountId, int days) + { + CompletableFuture future = CompletableFuture.supplyAsync(() -> + { + try (Connection connection = DBPool.getAccount().getConnection()) + { + try (PreparedStatement preparedStatement = connection.prepareStatement(GET_USER_RESULT)) + { + long accepted = getUserResult(preparedStatement, accountId, ReportResultType.ACCEPTED, days); + long denied = getUserResult(preparedStatement, accountId, ReportResultType.DENIED, days); + long flagged = getUserResult(preparedStatement, accountId, ReportResultType.ABUSIVE, days); + + return new ReportMetrics(accepted, denied, flagged); + } + } + catch (SQLException e) + { + throw new RuntimeException(e); + } + }); + + future.exceptionally(throwable -> + { + _logger.log(Level.SEVERE, "Error fetching user metrics.", throwable); + return null; + }); + + return future; + } + + private long getUserResult(PreparedStatement preparedStatement, int accountId, ReportResultType resultType, int days) throws SQLException + { + preparedStatement.setInt(1, accountId); + preparedStatement.setShort(2, resultType.getId()); + preparedStatement.setInt(3, days); + + try (ResultSet resultSet = preparedStatement.executeQuery()) + { + if (resultSet.next()) + { + return resultSet.getLong("amount"); + } + else + { + return 0; + } + } + } +}