Skip to content

Commit

Permalink
Started work on #3268
Browse files Browse the repository at this point in the history
TODO:
- Change the graph endpoint to accept multiple groups of addresses, or make multiple requests on frontend
- Add endpoint for getting list of join addresses
- Make the join address graph show the groups of addresses
- Clean up the code in the frontend big time
  • Loading branch information
AuroraLS3 committed Mar 23, 2024
1 parent 6d9494d commit 530c518
Show file tree
Hide file tree
Showing 12 changed files with 294 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package com.djrapitops.plan.delivery.domain;

import java.util.Objects;

/**
* Object that has a value tied to a date.
*
Expand All @@ -39,4 +41,25 @@ public long getDate() {
public T getValue() {
return value;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DateObj<?> dateObj = (DateObj<?>) o;
return getDate() == dateObj.getDate() && Objects.equals(getValue(), dateObj.getValue());
}

@Override
public int hashCode() {
return Objects.hash(getDate(), getValue());
}

@Override
public String toString() {
return "DateObj{" +
"date=" + date +
", value=" + value +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.djrapitops.plan.utilities.comparators.DateHolderOldestComparator;
import com.djrapitops.plan.utilities.comparators.PieSliceComparator;
import com.djrapitops.plan.utilities.dev.Untrusted;
import com.djrapitops.plan.utilities.java.Lists;
import com.djrapitops.plan.utilities.java.Maps;
import net.playeranalytics.plugin.scheduling.TimeAmount;
Expand Down Expand Up @@ -493,16 +494,16 @@ public void translateUnknown(Map<String, Integer> joinAddresses) {
}
}

public Map<String, Object> joinAddressesByDay(ServerUUID serverUUID, long after, long before) {
public Map<String, Object> joinAddressesByDay(ServerUUID serverUUID, long after, long before, @Untrusted List<String> addressFilter) {
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(serverUUID, config.getTimeZone().getOffset(System.currentTimeMillis()), after, before));
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(serverUUID, config.getTimeZone().getOffset(System.currentTimeMillis()), after, before, addressFilter));

return mapToJson(pieColors, joinAddresses);
}

public Map<String, Object> joinAddressesByDay(long after, long before) {
public Map<String, Object> joinAddressesByDay(long after, long before, @Untrusted List<String> addressFilter) {
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(config.getTimeZone().getOffset(System.currentTimeMillis()), after, before));
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(config.getTimeZone().getOffset(System.currentTimeMillis()), after, before, addressFilter));

return mapToJson(pieColors, joinAddresses);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,26 @@ public enum DataID {
EXTENSION_TABS,
EXTENSION_JSON,
LIST_SERVERS,
JOIN_ADDRESSES_BY_DAY,
JOIN_ADDRESSES_BY_DAY(false),
PLAYER_RETENTION,
PLAYER_JOIN_ADDRESSES,
PLAYER_ALLOWLIST_BOUNCES,
;

private final boolean cacheable;

DataID() {
this(true);
}

DataID(boolean cacheable) {
this.cacheable = cacheable;
}

public boolean isCacheable() {
return cacheable;
}

public String of(ServerUUID serverUUID) {
if (serverUUID == null) return name();
return name() + "_" + serverUUID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ public StoredJSON(String json, long timestamp) {
this.timestamp = timestamp;
}

public static StoredJSON fromObject(Object json, long timestamp) {
return new StoredJSON(new Gson().toJson(json), timestamp);
}

public String getJson() {
return json;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,16 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.apache.commons.lang3.StringUtils;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* Resolves /v1/graph JSON requests.
Expand Down Expand Up @@ -156,15 +161,22 @@ private JSONStorage.StoredJSON getGraphJSON(@Untrusted Request request, DataID d
JSONStorage.StoredJSON storedJSON;
if (request.getQuery().get("server").isPresent()) {
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
storedJSON = jsonResolverService.resolve(
timestamp, dataID, serverUUID,
theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID, request.getQuery())
);
Function<ServerUUID, Object> generationFunction = theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID, request.getQuery());
if (dataID.isCacheable()) {
storedJSON = jsonResolverService.resolve(timestamp, dataID, serverUUID, generationFunction);
} else {
storedJSON = JSONStorage.StoredJSON.fromObject(generationFunction.apply(serverUUID), System.currentTimeMillis());
}
} else {
// Assume network
storedJSON = jsonResolverService.resolve(
timestamp, dataID, () -> generateGraphDataJSONOfType(dataID, request.getQuery())
);
Supplier<Object> generationFunction = () -> generateGraphDataJSONOfType(dataID, request.getQuery());
if (dataID.isCacheable()) {
storedJSON = jsonResolverService.resolve(
timestamp, dataID, generationFunction
);
} else {
storedJSON = JSONStorage.StoredJSON.fromObject(generationFunction.get(), System.currentTimeMillis());
}
}
return storedJSON;
}
Expand Down Expand Up @@ -294,19 +306,24 @@ private Object generateGraphDataJSONOfType(DataID id, ServerUUID serverUUID, @Un
case GRAPH_PUNCHCARD:
return graphJSON.punchCardJSONAsMap(serverUUID);
case JOIN_ADDRESSES_BY_DAY:
try {
return graphJSON.joinAddressesByDay(serverUUID,
query.get("after").map(Long::parseLong).orElse(0L),
query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis())
);
} catch (@Untrusted NumberFormatException e) {
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
}
return joinAddressGraph(serverUUID, query);
default:
throw new BadRequestException("Graph type not supported with server-parameter (" + id.name() + ")");
}
}

private Map<String, Object> joinAddressGraph(ServerUUID serverUUID, @Untrusted URIQuery query) {
try {
Long after = query.get("after").map(Long::parseLong).orElse(0L);
Long before = query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis());
@Untrusted List<String> addressFilter = query.get("addresses").map(s -> StringUtils.split(s, ','))
.map(Arrays::asList).orElse(List.of());
return graphJSON.joinAddressesByDay(serverUUID, after, before, addressFilter);
} catch (@Untrusted NumberFormatException e) {
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
}
}

private Object generateGraphDataJSONOfType(DataID id, @Untrusted URIQuery query) {
switch (id) {
case GRAPH_ACTIVITY:
Expand All @@ -326,16 +343,21 @@ private Object generateGraphDataJSONOfType(DataID id, @Untrusted URIQuery query)
case GRAPH_ONLINE_PROXIES:
return graphJSON.proxyPlayersOnlineGraphs();
case JOIN_ADDRESSES_BY_DAY:
try {
return graphJSON.joinAddressesByDay(
query.get("after").map(Long::parseLong).orElse(0L),
query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis())
);
} catch (@Untrusted NumberFormatException e) {
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
}
return joinAddressGraph(query);
default:
throw new BadRequestException("Graph type not supported without server-parameter (" + id.name() + ")");
}
}

private Map<String, Object> joinAddressGraph(URIQuery query) {
try {
Long after = query.get("after").map(Long::parseLong).orElse(0L);
Long before = query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis());
@Untrusted List<String> addressFilter = query.get("addresses").map(s -> StringUtils.split(s, ','))
.map(Arrays::asList).orElse(List.of());
return graphJSON.joinAddressesByDay(after, before, addressFilter);
} catch (@Untrusted NumberFormatException e) {
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.djrapitops.plan.storage.database.sql.tables.SessionsTable;
import com.djrapitops.plan.storage.database.sql.tables.UsersTable;
import com.djrapitops.plan.utilities.dev.Untrusted;
import org.apache.commons.text.TextStringBuilder;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
Expand Down Expand Up @@ -162,21 +163,27 @@ public static Query<Set<Integer>> userIdsOfPlayersWithJoinAddresses(@Untrusted L
return db -> db.querySet(sql, RowExtractors.getInt(SessionsTable.USER_ID), joinAddresses.toArray());
}

public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(ServerUUID serverUUID, long timezoneOffset, long after, long before) {
public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(ServerUUID serverUUID, long timezoneOffset, long after, long before, @Untrusted List<String> addressFilter) {
return db -> {
Sql sql = db.getSql();

List<Integer> ids = db.query(joinAddressIds(addressFilter));
if (ids != null && ids.isEmpty()) return List.of();

String selectAddresses = SELECT +
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
"*1000 as date," +
JoinAddressTable.JOIN_ADDRESS +
JoinAddressTable.JOIN_ADDRESS + ',' +
SessionsTable.USER_ID +
", COUNT(1) as count" +
FROM + SessionsTable.TABLE_NAME + " s" +
LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
AND + SessionsTable.SESSION_START + ">?" +
AND + SessionsTable.SESSION_START + "<=?" +
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS;
(ids == null ? "" : AND + "j." + JoinAddressTable.ID +
" IN (" + new TextStringBuilder().appendWithSeparators(ids, ",").build() + ")") +
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS + ',' + SessionsTable.USER_ID;

return db.query(new QueryStatement<>(selectAddresses, 1000) {
@Override
Expand All @@ -193,9 +200,9 @@ public List<DateObj<Map<String, Integer>>> processResults(ResultSet set) throws
while (set.next()) {
long date = set.getLong("date");
String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS);
int count = set.getInt("count");
Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>());
joinAddresses.put(joinAddress, count);
// We ignore the count and get the number of players instead of sessions
joinAddresses.compute(joinAddress, (key, oldValue) -> oldValue != null ? oldValue + 1 : 1);
}

return addressesByDate.entrySet()
Expand All @@ -206,20 +213,37 @@ public List<DateObj<Map<String, Integer>>> processResults(ResultSet set) throws
};
}

public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(long timezoneOffset, long after, long before) {
public static Query<List<Integer>> joinAddressIds(@Untrusted List<String> addresses) {
return db -> {
if (addresses.isEmpty()) return null;

String selectJoinAddressIds = SELECT + JoinAddressTable.ID +
FROM + JoinAddressTable.TABLE_NAME +
WHERE + JoinAddressTable.JOIN_ADDRESS + " IN (" + Sql.nParameters(addresses.size()) + ")";
return db.queryList(selectJoinAddressIds, set -> set.getInt(JoinAddressTable.ID), addresses);
};
}

public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(long timezoneOffset, long after, long before, @Untrusted List<String> addressFilter) {
return db -> {
Sql sql = db.getSql();

List<Integer> ids = db.query(joinAddressIds(addressFilter));
if (ids != null && ids.isEmpty()) return List.of();

String selectAddresses = SELECT +
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
"*1000 as date," +
JoinAddressTable.JOIN_ADDRESS +
JoinAddressTable.JOIN_ADDRESS + ',' +
SessionsTable.USER_ID +
", COUNT(1) as count" +
FROM + SessionsTable.TABLE_NAME + " s" +
LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
WHERE + SessionsTable.SESSION_START + ">?" +
AND + SessionsTable.SESSION_START + "<=?" +
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS;
(ids == null ? "" : AND + "j." + JoinAddressTable.ID +
" IN (" + new TextStringBuilder().appendWithSeparators(ids, ",").build() + ")") +
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS + ',' + SessionsTable.USER_ID;

return db.query(new QueryStatement<>(selectAddresses, 1000) {
@Override
Expand All @@ -235,9 +259,9 @@ public List<DateObj<Map<String, Integer>>> processResults(ResultSet set) throws
while (set.next()) {
long date = set.getLong("date");
String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS);
int count = set.getInt("count");
Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>());
joinAddresses.put(joinAddress, count);
// We ignore the count and get the number of players instead of sessions
joinAddresses.compute(joinAddress, (key, oldValue) -> oldValue != null ? oldValue + 1 : 1);
}

return addressesByDate.entrySet()
Expand Down
Loading

0 comments on commit 530c518

Please sign in to comment.