From a27ef54105df24c6dac7bec2422e5ccf3e098eae Mon Sep 17 00:00:00 2001 From: Ashwin Rajesh <46510831+VanillaViking@users.noreply.github.com> Date: Thu, 10 Aug 2023 12:55:53 +1000 Subject: [PATCH] Create commands for old leaderboards, implement the changes for testsv2 (#76) and usersv2, and add some more indexes --- .../java/asynchronous/typing/AddTest.java | 6 +- .../java/asynchronous/typing/Leaderboard.java | 4 +- .../java/asynchronous/typing/TypeStats.java | 8 +- .../asynchronous/typing/TypingTestTeam.java | 2 +- .../typing/TypingTestTemplate.java | 6 +- .../dataStructures/LeaderboardConfig.java | 12 ++- src/main/java/zyenyo/Database.java | 85 +++++-------------- 7 files changed, 40 insertions(+), 83 deletions(-) diff --git a/src/main/java/asynchronous/typing/AddTest.java b/src/main/java/asynchronous/typing/AddTest.java index 1c8ccd6..c22f91e 100644 --- a/src/main/java/asynchronous/typing/AddTest.java +++ b/src/main/java/asynchronous/typing/AddTest.java @@ -3,7 +3,6 @@ import dataStructures.InfoCard; import net.dv8tion.jda.api.entities.MessageChannel; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; -import zyenyo.Database; public class AddTest implements Runnable { @@ -28,8 +27,9 @@ public void run() long userID; if (args.length == 4) {userID = event.getAuthor().getIdLong();} else {userID = Long.parseLong(args[4].substring(2, args[4].length() - 1));} // Only other case possible is args.length == 5. - - Database.addTest(userID, wpm, accuracy, tp); + + // this command is never used anyway + // Database.addTest(userID, wpm, accuracy, tp); channel.sendMessageFormat("_`[SA] Successfully added test.`_").queue(); } diff --git a/src/main/java/asynchronous/typing/Leaderboard.java b/src/main/java/asynchronous/typing/Leaderboard.java index 889e221..e0e9ac2 100644 --- a/src/main/java/asynchronous/typing/Leaderboard.java +++ b/src/main/java/asynchronous/typing/Leaderboard.java @@ -37,6 +37,7 @@ public void run() LeaderboardStatisticType lbStat = LeaderboardStatisticType.TP; LeaderboardScope lbScope = LeaderboardScope.SUM; int lbPage = 1; + Boolean old = false; for (String cmd : args) { switch (cmd.toLowerCase()) { @@ -51,10 +52,11 @@ public void run() case "-p": lbPage = getValueArg(args, "-p"); break; case "-page": lbPage = getValueArg(args, "-page"); break; case "-tests": lbStat = LeaderboardStatisticType.TESTS; break; + case "-old": old = true; break; } } - LeaderboardConfig lbConfig = new LeaderboardConfig(lbStat, lbScope); + LeaderboardConfig lbConfig = new LeaderboardConfig(lbStat, lbScope, old); AggregateIterable lbList = Database.getLeaderboards(lbConfig); diff --git a/src/main/java/asynchronous/typing/TypeStats.java b/src/main/java/asynchronous/typing/TypeStats.java index 97de93f..a7b6ef7 100644 --- a/src/main/java/asynchronous/typing/TypeStats.java +++ b/src/main/java/asynchronous/typing/TypeStats.java @@ -65,7 +65,7 @@ else if (args.length == 3 && args[2].equals("-g")) } else { - json = new JSONObject(Database.getStats(idStr)); + json = new JSONObject(Database.getStats(idStr, false)); title = "Recent Typing Statistics for " + event.getJDA().retrieveUserById(id).submit().get().getAsTag(); } @@ -74,7 +74,7 @@ else if (args.length == 3 && args[2].equals("-g")) double bestWpm = Double.parseDouble(json.get("bestWpm").toString()); double deviation = Double.parseDouble(json.get("deviation").toString()); double typingPoints = Double.parseDouble(json.get("weightedTp").toString()); -// double playtime = Double.parseDouble(json.get("playtime").toString()); + double playtime = Double.parseDouble(json.get("playtime").toString()); String rank = getRank(averageWpm); channel.sendMessageEmbeds(new EmbedBuilder() @@ -85,9 +85,9 @@ else if (args.length == 3 && args[2].equals("-g")) + "Average WPM: **`%.2f`**%n" + "Deviation: **`%.2f`**%n" + "Average Accuracy: **`%.2f%%`**%n" -// + "Playtime: **`%.0f hours %.0f minutes`**%n" + + "Playtime: **`%.0f hours %.0f minutes`**%n" + "Rank: **`%s`**", - testsTaken, typingPoints, bestWpm, averageWpm, deviation, averageAcc, /* playtime / (1000 * 60 * 60), playtime / (1000 * 60), */ rank), false) + testsTaken, typingPoints, bestWpm, averageWpm, deviation, averageAcc, playtime / (1000 * 60 * 60), playtime / (1000 * 60), rank), false) .setColor(new Color(180, 50, 80)) .build()) .queue(); diff --git a/src/main/java/asynchronous/typing/TypingTestTeam.java b/src/main/java/asynchronous/typing/TypingTestTeam.java index bb98271..6cb030d 100644 --- a/src/main/java/asynchronous/typing/TypingTestTeam.java +++ b/src/main/java/asynchronous/typing/TypingTestTeam.java @@ -142,7 +142,7 @@ else if (Arrays.asList(teamBlue).contains(String.format("<@%s>", s.userID()))) } else {continue;} - Database.addTest(s.userID(), s.wordsPerMinute(), s.accuracy(), s.typingPoints()); + Database.addTestV2(s); } int i = 0; diff --git a/src/main/java/asynchronous/typing/TypingTestTemplate.java b/src/main/java/asynchronous/typing/TypingTestTemplate.java index f2ade46..a2b0d73 100644 --- a/src/main/java/asynchronous/typing/TypingTestTemplate.java +++ b/src/main/java/asynchronous/typing/TypingTestTemplate.java @@ -206,9 +206,7 @@ public void run() for (int i = 0; i < submissions.getNumSubmissions(); i++) { TypingSubmission s = submissions.getSubmission(lbOrder.get(i)); - AddTestResult result = Database.addTest(s.userID(), s.wordsPerMinute(), s.accuracy(), s.typingPoints()); - // TODO: replace with this after tpv2 - // AddTestResult result = Database.addTestV2(s) + AddTestResult result = Database.addTestV2(s); String dailyStreak = ""; if (result.dailyStreak() > 0) { dailyStreak = String.format("Daily Streak: **`%d`**", result.dailyStreak()); @@ -232,4 +230,4 @@ public void run() }; } -record TpCalculation(double typingPoints, double wordsPerMinute, double accuracy) {} \ No newline at end of file +record TpCalculation(double typingPoints, double wordsPerMinute, double accuracy) {} diff --git a/src/main/java/dataStructures/LeaderboardConfig.java b/src/main/java/dataStructures/LeaderboardConfig.java index ddcc31b..7f11360 100644 --- a/src/main/java/dataStructures/LeaderboardConfig.java +++ b/src/main/java/dataStructures/LeaderboardConfig.java @@ -1,6 +1,7 @@ package dataStructures; import com.mongodb.client.model.Accumulators; +import com.mongodb.client.model.Aggregates; import com.mongodb.client.model.BsonField; import java.util.ArrayList; @@ -15,11 +16,14 @@ public class LeaderboardConfig { private LeaderboardScope lbScope; private String lbStatistic; - private String collection = "tests"; + private String collection; + Boolean old; private ArrayList accumulators = new ArrayList(); - public LeaderboardConfig(LeaderboardStatisticType lbStatistic, LeaderboardScope lbScope) { + public LeaderboardConfig(LeaderboardStatisticType lbStatistic, LeaderboardScope lbScope, Boolean old) { this.lbScope = lbScope; + this.old = old; + this.collection = old ? "tests" : "testsv2"; switch (lbStatistic) { case TP: @@ -27,7 +31,7 @@ public LeaderboardConfig(LeaderboardStatisticType lbStatistic, LeaderboardScope switch(lbScope) { case SUM: this.lbStatistic = "totalTp"; - this.collection = "users"; + this.collection = old ?"users" : "usersv2"; this.accumulators.add(Accumulators.sum(this.lbStatistic, "$" + this.lbStatistic)); this.accumulators.add(Accumulators.first("userTag", "$userTag")); break; @@ -85,7 +89,7 @@ public List getAccumulationStrategies() { } public String getLeaderboardTitle() { - return String.format("Global %s %s Leaderboards", StringUtils.capitalize(lbScope.toString().toLowerCase()), StringUtils.capitalize(String.join(" ", StringUtils.splitByCharacterTypeCamelCase(this.lbStatistic)))); + return String.format("Global %s %s Leaderboards %s", StringUtils.capitalize(lbScope.toString().toLowerCase()), StringUtils.capitalize(String.join(" ", StringUtils.splitByCharacterTypeCamelCase(this.lbStatistic))), this.old ? "(old)" : ""); } } diff --git a/src/main/java/zyenyo/Database.java b/src/main/java/zyenyo/Database.java index 75c524a..6de88e2 100644 --- a/src/main/java/zyenyo/Database.java +++ b/src/main/java/zyenyo/Database.java @@ -56,6 +56,7 @@ public class Database private static MongoClient client; private static MongoCollection tests; private static MongoCollection testsV2; + private static MongoCollection usersV2; private static MongoCollection users; private static MongoCollection prompts; @@ -76,74 +77,23 @@ public static void connect(String uri, String ENVIRONMENT) tests = client.getDatabase(DB_NAME).getCollection("tests"); testsV2 = client.getDatabase(DB_NAME).getCollection("testsv2"); - // tests will be queried very often for a user's top 100 tp scores. This needs to be fast. - testsV2.createIndex(Indexes.descending("tp")); users = client.getDatabase(DB_NAME).getCollection("users"); - users.createIndex(Indexes.descending("totalTp")); + usersV2 = client.getDatabase(DB_NAME).getCollection("usersv2"); prompts = client.getDatabase(DB_NAME).getCollection("prompts"); - } - - public static AddTestResult addTest(long discordId, double wpm, double accuracy, double tp) - { - double initialWeightedTp = getWeightedTp(discordId); - ArrayList userUpdates = new ArrayList(); - - InsertOneResult result = tests.insertOne(new Document() - .append("_id", new ObjectId()) - .append("discordId", String.valueOf(discordId)) - .append("wpm", wpm) - .append("accuracy", accuracy) - .append("tp", tp) - .append("date", LocalDateTime.now()) - ); - - double newWeightedTp = getWeightedTp(discordId); - userUpdates.add(Updates.set("totalTp", newWeightedTp)); - - streakStatusResult streak = getStreakStatus(String.valueOf(discordId)); - switch (streak.status()) { - case AVAILABLE: - userUpdates.add(Updates.inc("daily.currentStreak", 1)); - // some other time :) - // userUpdates.add(Updates.max("daily.maxStreak", )); - userUpdates.add(Updates.set("daily.test", result.getInsertedId())); - // storing an instant instead of a normal date here otherwise it would push the server's local timezone to the db instead of UTC. - userUpdates.add(Updates.set("daily.updatedAt", new Date().toInstant().toString())); - break; - case CLAIMED: - break; - case INITIAL: - userUpdates.add(Updates.set("daily", new Document() - .append("currentStreak", 1) - .append("maxStreak", 1) - .append("test", result.getInsertedId()) - .append("updatedAt", new Date().toInstant().toString()) - )); - break; - case MISSED: - userUpdates.add(Updates.set("daily.currentStreak", 1)); - userUpdates.add(Updates.set("daily.test", result.getInsertedId())); - userUpdates.add(Updates.set("daily.updatedAt", new Date().toInstant().toString())); - break; - default: - break; - - } + + // Indexes for common statistics in order to speed up leaderboard commands + testsV2.createIndex(Indexes.descending("tp")); + testsV2.createIndex(Indexes.descending("wpm")); + testsV2.createIndex(Indexes.descending("accuracy")); + testsV2.createIndex(Indexes.descending("date")); - users.updateOne( - Filters.eq("discordId", String.valueOf(discordId)), - Updates.combine( - userUpdates - ), - upsertTrue - ); + usersV2.createIndex(Indexes.descending("totalTp")); + usersV2.createIndex(Indexes.descending("playtime")); - return new AddTestResult(newWeightedTp - initialWeightedTp, streak.currentStreak()); } - // TODO: make this the default addtest when tpv2 is done public static AddTestResult addTestV2(TypingSubmission submission) { double initialWeightedTp = getWeightedTp(submission.userID()); @@ -196,7 +146,7 @@ public static AddTestResult addTestV2(TypingSubmission submission) { userUpdates.add(Updates.inc("playtime", submission.timeTakenMillis())); - users.updateOne( + usersV2.updateOne( Filters.eq("discordId", String.valueOf(submission.userID())), Updates.combine( userUpdates @@ -287,8 +237,12 @@ public static RefreshUserNamesResult refreshUserNames(MessageReceivedEvent event return new RefreshUserNamesResult(timeTakenMillis, outdatedTagsCount); } - public static String getStats(String discordId) + public static String getStats(String discordId, Boolean old) { + + MongoCollection tests = client.getDatabase(DB_NAME).getCollection(old ? "tests" : "testsv2"); + MongoCollection users = client.getDatabase(DB_NAME).getCollection(old ? "users" : "usersv2"); + tests.find(Filters.eq("discordId", discordId)).sort(descending("tp")); Document stats = tests.aggregate(Arrays.asList( @@ -301,9 +255,8 @@ public static String getStats(String discordId) Accumulators.max("bestWpm", "$wpm"), Accumulators.stdDevPop("deviation", "$wpm") ), - Aggregates.set(new Field("weightedTp", getWeightedTp(Long.valueOf(discordId)))) - // For TPv2: -// Aggregates.set(new Field("playtime", users.find(Filters.eq("discordId", discordId)).first().getDouble("playtime"))) + Aggregates.set(new Field("weightedTp", getWeightedTp(Long.valueOf(discordId)))), + Aggregates.set(new Field("playtime", users.find(Filters.eq("discordId", discordId)).first().getDouble("playtime"))) )).first(); @@ -327,7 +280,7 @@ public static AggregateIterable getLeaderboards(LeaderboardConfig lbCo private static double getWeightedTp(long id) { - AggregateIterable tpList = tests.aggregate(Arrays.asList( + AggregateIterable tpList = testsV2.aggregate(Arrays.asList( Aggregates.match(Filters.eq("discordId", String.valueOf(id))), Aggregates.match(Filters.exists("tp")), Aggregates.sort(descending("tp")),