diff --git a/Plan/common/src/main/java/com/djrapitops/plan/data/store/keys/SessionKeys.java b/Plan/common/src/main/java/com/djrapitops/plan/data/store/keys/SessionKeys.java index 9315770dd..514b28d76 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/data/store/keys/SessionKeys.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/data/store/keys/SessionKeys.java @@ -35,6 +35,8 @@ public class SessionKeys { public static final Key DB_ID = new Key<>(Integer.class, "db_id"); public static final Key UUID = CommonKeys.UUID; public static final Key SERVER_UUID = CommonKeys.SERVER_UUID; + public static final Key NAME = CommonKeys.NAME; + public static final Key SERVER_NAME = new Key<>(String.class, "server_name"); public static final Key START = new Key<>(Long.class, "start"); public static final Key END = new Key<>(Long.class, "end"); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/SessionsMutator.java b/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/SessionsMutator.java index a5ac0c11f..f562c6506 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/SessionsMutator.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/SessionsMutator.java @@ -23,7 +23,11 @@ import com.djrapitops.plan.data.store.containers.DataContainer; import com.djrapitops.plan.data.store.keys.CommonKeys; import com.djrapitops.plan.data.store.keys.SessionKeys; import com.djrapitops.plan.data.time.WorldTimes; +import com.djrapitops.plan.system.settings.config.WorldAliasSettings; import com.djrapitops.plan.utilities.analysis.Median; +import com.djrapitops.plan.utilities.formatting.Formatters; +import com.djrapitops.plan.utilities.html.graphs.Graphs; +import com.djrapitops.plan.utilities.html.graphs.pie.WorldPie; import java.util.*; import java.util.function.Predicate; @@ -243,4 +247,39 @@ public class SessionsMutator { .mapToDouble(Optional::get) .average().orElse(0.0); } + + public List> toPlayerJSONMaps( + Graphs graphs, + WorldAliasSettings worldAliasSettings, + Formatters formatters + ) { + return sessions.stream().map(session -> { + Map sessionMap = new HashMap<>(); + sessionMap.put("player_name", session.getValue(SessionKeys.NAME).orElse(session.getUnsafe(SessionKeys.UUID).toString())); + sessionMap.put("name", sessionMap.get("player_name")); + sessionMap.put("server_name", session.getValue(SessionKeys.SERVER_NAME).orElse(session.getUnsafe(SessionKeys.SERVER_UUID).toString())); + sessionMap.put("start", session.getValue(SessionKeys.START).map(formatters.yearLong()).orElse("-") + + (session.supports(SessionKeys.END) ? "" : " (Online)")); + sessionMap.put("end", session.getValue(SessionKeys.END).map(formatters.yearLong()).orElse("Online")); + sessionMap.put("most_used_world", worldAliasSettings.getLongestWorldPlayed(session)); + sessionMap.put("length", session.getValue(SessionKeys.LENGTH).map(formatters.timeAmount()).orElse("-")); + sessionMap.put("afk_time", session.getValue(SessionKeys.AFK_TIME).map(formatters.timeAmount()).orElse("-")); + sessionMap.put("mob_kills", session.getValue(SessionKeys.MOB_KILL_COUNT).orElse(0)); + sessionMap.put("deaths", session.getValue(SessionKeys.DEATH_COUNT).orElse(0)); + sessionMap.put("player_kills", session.getPlayerKills().stream().map( + kill -> { + Map killMap = new HashMap<>(); + killMap.put("date", formatters.secondLong().apply(kill.getDate())); + killMap.put("victim", kill.getVictimName()); + killMap.put("killer", sessionMap.get("player_name")); + killMap.put("weapon", kill.getWeapon()); + return killMap; + } + ).collect(Collectors.toList())); + WorldPie worldPie = graphs.pie().worldPie(session.getValue(SessionKeys.WORLD_TIMES).orElse(new WorldTimes())); + sessionMap.put("world_series", worldPie.getSlices()); + sessionMap.put("gm_series", worldPie.toHighChartsDrillDownMaps()); + return sessionMap; + }).collect(Collectors.toList()); + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/db/access/queries/objects/SessionQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/db/access/queries/objects/SessionQueries.java index 44a057412..12a9b88b6 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/db/access/queries/objects/SessionQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/db/access/queries/objects/SessionQueries.java @@ -295,4 +295,29 @@ public class SessionQueries { } }; } + + public static Query> fetchLatestSessionsOfServer(UUID serverUUID, int limit) { + String selectLastDateToInclude = SELECT + SessionsTable.TABLE_NAME + '.' + SessionsTable.SESSION_START + + FROM + SessionsTable.TABLE_NAME + + WHERE + SessionsTable.TABLE_NAME + '.' + SessionsTable.SERVER_UUID + "=?" + + ORDER_BY_SESSION_START_DESC + " LIMIT 1 OFFSET ?"; + + String sql = SELECT_SESSIONS_STATEMENT + + WHERE + SessionsTable.TABLE_NAME + '.' + SessionsTable.SESSION_START + ">=(" + selectLastDateToInclude + ')' + + AND + SessionsTable.TABLE_NAME + '.' + SessionsTable.SERVER_UUID + "=?"; + + return new QueryStatement>(sql, limit) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setInt(2, limit); + statement.setString(3, serverUUID.toString()); + } + + @Override + public List processResults(ResultSet set) throws SQLException { + return extractDataFromSessionSelectStatement(set); + } + }; + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/system/json/JSONFactory.java b/Plan/common/src/main/java/com/djrapitops/plan/system/json/JSONFactory.java index 746616951..610716d47 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/system/json/JSONFactory.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/system/json/JSONFactory.java @@ -16,17 +16,23 @@ */ package com.djrapitops.plan.system.json; +import com.djrapitops.plan.data.container.Session; +import com.djrapitops.plan.data.store.mutators.SessionsMutator; import com.djrapitops.plan.db.Database; import com.djrapitops.plan.db.access.queries.containers.ServerPlayersTableContainersQuery; +import com.djrapitops.plan.db.access.queries.objects.SessionQueries; import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionServerPlayerDataTableQuery; import com.djrapitops.plan.system.database.DBSystem; import com.djrapitops.plan.system.settings.config.PlanConfig; import com.djrapitops.plan.system.settings.paths.DisplaySettings; import com.djrapitops.plan.system.settings.paths.TimeSettings; import com.djrapitops.plan.utilities.formatting.Formatters; +import com.djrapitops.plan.utilities.html.graphs.Graphs; import javax.inject.Inject; import javax.inject.Singleton; +import java.util.List; +import java.util.Map; import java.util.UUID; /** @@ -39,16 +45,19 @@ public class JSONFactory { private final PlanConfig config; private final DBSystem dbSystem; + private final Graphs graphs; private final Formatters formatters; @Inject public JSONFactory( PlanConfig config, DBSystem dbSystem, + Graphs graphs, Formatters formatters ) { this.config = config; this.dbSystem = dbSystem; + this.graphs = graphs; this.formatters = formatters; } @@ -67,4 +76,12 @@ public class JSONFactory { formatters ).toJSONString(); } + + public List> serverSessionsAsJSONMap(UUID serverUUID) { + Database db = dbSystem.getDatabase(); + List sessions = db.query(SessionQueries.fetchLatestSessionsOfServer( + serverUUID, config.get(DisplaySettings.SESSIONS_PER_PAGE) + )); + return new SessionsMutator(sessions).toPlayerJSONMaps(graphs, config.getWorldAliasSettings(), formatters); + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/system/webserver/pages/json/RootJSONHandler.java b/Plan/common/src/main/java/com/djrapitops/plan/system/webserver/pages/json/RootJSONHandler.java index 43c94333c..0d9a93b1e 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/system/webserver/pages/json/RootJSONHandler.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/system/webserver/pages/json/RootJSONHandler.java @@ -37,6 +37,7 @@ public class RootJSONHandler extends TreePageHandler { public RootJSONHandler( ResponseFactory responseFactory, GraphsJSONHandler graphsJSONHandler, + SessionsJSONHandler sessionsJSONHandler, PlayersTableJSONHandler playersTableJSONHandler, ServerOverviewJSONHandler serverOverviewJSONHandler, OnlineActivityOverviewJSONHandler onlineActivityOverviewJSONHandler @@ -44,6 +45,7 @@ public class RootJSONHandler extends TreePageHandler { super(responseFactory); registerPage("players", playersTableJSONHandler); + registerPage("sessions", sessionsJSONHandler); registerPage("graph", graphsJSONHandler); registerPage("serverOverview", serverOverviewJSONHandler); registerPage("onlineOverview", onlineActivityOverviewJSONHandler); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/system/webserver/pages/json/ServerParameterJSONHandler.java b/Plan/common/src/main/java/com/djrapitops/plan/system/webserver/pages/json/ServerParameterJSONHandler.java deleted file mode 100644 index 0029ba928..000000000 --- a/Plan/common/src/main/java/com/djrapitops/plan/system/webserver/pages/json/ServerParameterJSONHandler.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * This file is part of Player Analytics (Plan). - * - * Plan is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License v3 as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Plan is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Plan. If not, see . - */ -package com.djrapitops.plan.system.webserver.pages.json; - -import com.djrapitops.plan.api.exceptions.connection.BadRequestException; -import com.djrapitops.plan.db.access.queries.objects.ServerQueries; -import com.djrapitops.plan.system.database.DBSystem; -import com.djrapitops.plan.system.info.server.Server; -import com.djrapitops.plan.system.webserver.RequestTarget; -import com.djrapitops.plan.system.webserver.pages.PageHandler; - -import java.util.Optional; -import java.util.UUID; - -/** - * JSON handler for different graph data JSON requests. - * - * @author Rsl1122 - */ -public abstract class ServerParameterJSONHandler implements PageHandler { - - protected final DBSystem dbSystem; - - protected ServerParameterJSONHandler(DBSystem dbSystem) { - this.dbSystem = dbSystem; - } - - protected UUID getServerUUID(RequestTarget target) throws BadRequestException { - Optional serverUUID = target.getParameter("serverUUID"); - if (serverUUID.isPresent()) { - return getServerUUIDDirectly(serverUUID.get()); - } else { - return getServerUUIDFromName(target); // Preferred - } - } - - protected UUID getServerUUIDFromName(RequestTarget target) throws BadRequestException { - String serverName = target.getParameter("serverName") - .orElseThrow(() -> new BadRequestException("'serverName' parameter was not defined.")); - return dbSystem.getDatabase().query(ServerQueries.fetchServerMatchingIdentifier(serverName)) - .map(Server::getUuid) - .orElseThrow(() -> new BadRequestException("'serverName' was not found in the database.: '" + serverName + "'")); - } - - protected UUID getServerUUIDDirectly(String serverUUIDString) throws BadRequestException { - try { - return UUID.fromString(serverUUIDString); - } catch (IllegalArgumentException malformedUUIDException) { - throw new BadRequestException("'serverName' was not a valid UUID: " + malformedUUIDException.getMessage()); - } - } -} \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/system/webserver/pages/json/SessionsJSONHandler.java b/Plan/common/src/main/java/com/djrapitops/plan/system/webserver/pages/json/SessionsJSONHandler.java new file mode 100644 index 000000000..0f5abac6d --- /dev/null +++ b/Plan/common/src/main/java/com/djrapitops/plan/system/webserver/pages/json/SessionsJSONHandler.java @@ -0,0 +1,65 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.system.webserver.pages.json; + +import com.djrapitops.plan.api.exceptions.WebUserAuthException; +import com.djrapitops.plan.api.exceptions.connection.WebException; +import com.djrapitops.plan.system.Identifiers; +import com.djrapitops.plan.system.json.JSONFactory; +import com.djrapitops.plan.system.webserver.Request; +import com.djrapitops.plan.system.webserver.RequestTarget; +import com.djrapitops.plan.system.webserver.auth.Authentication; +import com.djrapitops.plan.system.webserver.pages.PageHandler; +import com.djrapitops.plan.system.webserver.response.Response; +import com.djrapitops.plan.system.webserver.response.data.JSONResponse; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Collections; +import java.util.UUID; + +/** + * Performs parameter parsing for Sessions JSON requests. + * + * @author Rsl1122 + */ +@Singleton +public class SessionsJSONHandler implements PageHandler { + + private final Identifiers identifiers; + private final JSONFactory jsonFactory; + + @Inject + public SessionsJSONHandler( + Identifiers identifiers, + JSONFactory jsonFactory + ) { + this.identifiers = identifiers; + this.jsonFactory = jsonFactory; + } + + @Override + public Response getResponse(Request request, RequestTarget target) throws WebException { + UUID serverUUID = identifiers.getServerUUID(target); + return new JSONResponse<>(Collections.singletonMap("sessions", jsonFactory.serverSessionsAsJSONMap(serverUUID))); + } + + @Override + public boolean isAuthorized(Authentication auth, RequestTarget target) throws WebUserAuthException { + return auth.getWebUser().getPermLevel() <= 0; + } +} \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/Pie.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/Pie.java index d9f739081..91068eb91 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/Pie.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/Pie.java @@ -40,4 +40,8 @@ public class Pie implements HighChart { series.appendWithSeparators(slices, ","); return series.append("]").toString(); } + + public List getSlices() { + return slices; + } } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/PieGraphFactory.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/PieGraphFactory.java index e9b340c72..9c187fa12 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/PieGraphFactory.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/PieGraphFactory.java @@ -26,6 +26,7 @@ import com.djrapitops.plan.system.settings.theme.ThemeVal; import javax.inject.Inject; import javax.inject.Singleton; +import java.util.Arrays; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -63,7 +64,9 @@ public class PieGraphFactory { WorldAliasSettings worldAliasSettings = config.getWorldAliasSettings(); Map playtimePerAlias = worldAliasSettings.getPlaytimePerAlias(worldTimes); Map gmTimesPerAlias = worldAliasSettings.getGMTimesPerAlias(worldTimes); - String[] colors = theme.getValue(ThemeVal.GRAPH_WORLD_PIE).split(", "); + String[] colors = Arrays.stream(theme.getValue(ThemeVal.GRAPH_WORLD_PIE).split(",")) + .map(color -> color.trim().replace("\"", "")) + .toArray(String[]::new); boolean orderByPercentage = config.isTrue(DisplaySettings.ORDER_WORLD_PIE_BY_PERC); return new WorldPie(playtimePerAlias, gmTimesPerAlias, colors, orderByPercentage); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/PieSlice.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/PieSlice.java index f39ec23c0..771e4eba6 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/PieSlice.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/PieSlice.java @@ -25,7 +25,7 @@ public class PieSlice { private final String name; private final long y; private final String color; - private final boolean drilldown; + private final String drilldown; public PieSlice(String name, long y) { this(name, y, null, false); @@ -43,7 +43,7 @@ public class PieSlice { this.name = name; this.y = y; this.color = color; - this.drilldown = drilldown; + this.drilldown = drilldown ? name : null; } @Override @@ -51,7 +51,7 @@ public class PieSlice { return "{name:'" + name + "'," + "y:" + y + (color != null ? "," + "color:" + color : "") - + (drilldown ? "," + "drilldown: '" + name + "'" : "") + + (drilldown != null ? "," + "drilldown: '" + drilldown + "'" : "") + "}"; } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/WorldPie.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/WorldPie.java index 4dd2d75d9..374311cf7 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/WorldPie.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/graphs/pie/WorldPie.java @@ -19,10 +19,7 @@ package com.djrapitops.plan.utilities.html.graphs.pie; import com.djrapitops.plan.data.time.GMTimes; import com.djrapitops.plan.utilities.comparators.PieSliceComparator; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; public class WorldPie extends PieWithDrilldown { @@ -62,6 +59,27 @@ public class WorldPie extends PieWithDrilldown { return slices; } + public List> toHighChartsDrillDownMaps() { + List> drilldowns = new ArrayList<>(); + for (Map.Entry worldAlias : gmTimesAliasMap.entrySet()) { + Map drilldown = new HashMap<>(); + drilldown.put("name", worldAlias.getKey()); + drilldown.put("id", worldAlias.getKey()); + drilldown.put("data", parseGMTimesForWorld(worldAlias.getValue())); + drilldowns.add(drilldown); + } + return drilldowns; + } + + private List parseGMTimesForWorld(GMTimes gmTimes) { + List data = new ArrayList<>(); + for (Map.Entry gmEntry : gmTimes.getTimes().entrySet()) { + List gmList = Arrays.asList(gmEntry.getKey(), gmEntry.getValue()); + data.add(gmList); + } + return data; + } + @Override public String toHighChartsDrilldown() { StringBuilder drilldownBuilder = new StringBuilder(); diff --git a/Plan/common/src/main/resources/assets/plan/web/js/charts/worldPie.js b/Plan/common/src/main/resources/assets/plan/web/js/charts/worldPie.js index e00c5b447..b1d988e4b 100644 --- a/Plan/common/src/main/resources/assets/plan/web/js/charts/worldPie.js +++ b/Plan/common/src/main/resources/assets/plan/web/js/charts/worldPie.js @@ -38,7 +38,9 @@ function worldPie(id, worldSeries, gmSeries) { }, series: [worldSeries], drilldown: { - series: gmSeries + series: gmSeries.map(function (d) { + return {name: d.name, id: d.id, colors: gmPieColors, data: d.data} + }) } }); } diff --git a/Plan/common/src/main/resources/assets/plan/web/server.html b/Plan/common/src/main/resources/assets/plan/web/server.html index 692b1d406..d9f7e6b32 100644 --- a/Plan/common/src/main/resources/assets/plan/web/server.html +++ b/Plan/common/src/main/resources/assets/plan/web/server.html @@ -1835,32 +1835,7 @@ x.style.opacity = "1"; openFunc(slideIndex)(); - loadSessionAccordion({ - sessions: [ - { - name: 'Rsl1122', - player_name: 'Rsl1122', - server_name: "Test server", - start: "Oct 23, 4:32", - end: "Oct 23, 5:34", - most_used_world: "World (100%)", - length: "1h 2m", - afk_time: "5m", - mob_kills: 4, - deaths: 0, - player_kills: [ - { - date: "Oct 23 4:50", - victim: "Bill", - killer: "Rsl1122", - weapon: "Diamond Sword" - } - ], - world_series: [{name: 'World', y: 4320}], - gm_series: [] - } - ] - }, null); + jsonRequest("../v1/sessions?serverName=${serverName}", loadSessionAccordion); setLoadingText('Done.'); setTimeout(function () { diff --git a/Plan/json-endpoints.md b/Plan/json-endpoints.md index eef554e1a..c15247fc1 100644 --- a/Plan/json-endpoints.md +++ b/Plan/json-endpoints.md @@ -49,6 +49,17 @@ Parameter|Expected value|Description `serverName` | Name of a Plan server | Used for identifying Plan server that the data should be about `serverUUID` | UUID of a Plan server | Used for identifying Plan server that the data should be about +### `GET /v1/sessions` + +Obtain data for `/server` session accordion. Returns configurable amount of sessions. + +Required parameters: `serverName` or `serverUUID` + +Parameter|Expected value|Description +--|--|-- +`serverName` | Name of a Plan server | Used for identifying Plan server that the data should be about +`serverUUID` | UUID of a Plan server | Used for identifying Plan server that the data should be about + ### `GET /v1/graph` Obtain data for graphs. @@ -64,5 +75,5 @@ Parameter|Expected value|Description Type | Description -- | -- `performance` | TPS data points for last 6 months: Players Online, TPS, CPU, RAM, Chunks, Entities, Disk Space -`uniqueAndNew` | Player data points for each day, how many unique and how many new players were there each day. - +`uniqueAndNew` | Player data points for each day, how many unique and how many new players were there each day. Last 180 days +`calendar` | Calendar data points for each day there is data for. Last 2 years.