Skip to content

Commit

Permalink
3268/redesign join address visualization (#3558)
Browse files Browse the repository at this point in the history
- Join address pie removed
- Join address group mechanism added
  - User can select multiple addresses for each group
  - User can rename each group to their liking
  - The groups are stored in preferences so that user doesn't need to add them back every time
- Use the join address group mechanism for time series of Join Addresses
- Use the join address group mechanism for Player Retention
- Small improvement to retention graph: Show multiple labels
- Small improvement to site clock: Can now hover to show actual date

Affected issues:
- Close #3268
  • Loading branch information
AuroraLS3 committed Apr 7, 2024
1 parent b6f6893 commit f40e149
Show file tree
Hide file tree
Showing 57 changed files with 814 additions and 357 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 @@ -19,6 +19,9 @@
import com.djrapitops.plan.settings.locale.lang.Lang;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
Expand Down Expand Up @@ -47,7 +50,8 @@ public enum WebPermission implements Supplier<String>, Lang {
PAGE_NETWORK_SESSIONS_LIST("See list of sessions"),
PAGE_NETWORK_JOIN_ADDRESSES("See Join Addresses -tab"),
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph"),
@Deprecated
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph", true),
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"),
PAGE_NETWORK_RETENTION("See Player Retention -tab"),
PAGE_NETWORK_GEOLOCATIONS("See Geolocations tab"),
Expand Down Expand Up @@ -82,7 +86,8 @@ public enum WebPermission implements Supplier<String>, Lang {
PAGE_SERVER_SESSIONS_LIST("See list of sessions"),
PAGE_SERVER_JOIN_ADDRESSES("See Join Addresses -tab"),
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph"),
@Deprecated
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph", true),
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"),
PAGE_SERVER_RETENTION("See Player Retention -tab"),
PAGE_SERVER_GEOLOCATIONS("See Geolocations tab"),
Expand Down Expand Up @@ -156,4 +161,23 @@ public String getKey() {
public String getDefault() {
return description;
}

public static WebPermission[] nonDeprecatedValues() {
return Arrays.stream(values())
.filter(Predicate.not(WebPermission::isDeprecated))
.toArray(WebPermission[]::new);
}

public static Optional<WebPermission> findByPermission(String permission) {
String name = StringUtils.upperCase(permission).replace('.', '_');
try {
return Optional.of(valueOf(name));
} catch (IllegalArgumentException noSuchEnum) {
return Optional.empty();
}
}

public static boolean isDeprecated(String permission) {
return findByPermission(permission).map(WebPermission::isDeprecated).orElse(false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.delivery.domain.datatransfer;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
* Represents data returned by {@link com.djrapitops.plan.delivery.webserver.resolver.json.PlayerJoinAddressJSONResolver}.
*
* @author AuroraLS3
*/
public class PlayerJoinAddresses {

private final List<String> joinAddresses;
private final Map<UUID, String> joinAddressByPlayer;

public PlayerJoinAddresses(List<String> joinAddresses, Map<UUID, String> joinAddressByPlayer) {
this.joinAddresses = joinAddresses;
this.joinAddressByPlayer = joinAddressByPlayer;
}

public List<String> getJoinAddresses() {
return joinAddresses;
}

public Map<UUID, String> getJoinAddressByPlayer() {
return joinAddressByPlayer;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PlayerJoinAddresses that = (PlayerJoinAddresses) o;
return Objects.equals(getJoinAddresses(), that.getJoinAddresses()) && Objects.equals(getJoinAddressByPlayer(), that.getJoinAddressByPlayer());
}

@Override
public int hashCode() {
return Objects.hash(getJoinAddresses(), getJoinAddressByPlayer());
}

@Override
public String toString() {
return "PlayerJoinAddresses{" +
"joinAddresses=" + joinAddresses +
", joinAddressByPlayer=" + joinAddressByPlayer +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ public void exportJSON(ExportPaths exportPaths, Path toDirectory, Server server)
"graph?type=uniqueAndNew",
"graph?type=hourlyUniqueAndNew",
"graph?type=serverPie",
"graph?type=joinAddressPie",
"graph?type=joinAddressByDay",
"graph?type=activity",
"graph?type=geolocation",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ public void exportJSON(Path toDirectory, Server server) throws IOException {
"graph?type=geolocation&server=" + serverUUID,
"graph?type=uniqueAndNew&server=" + serverUUID,
"graph?type=hourlyUniqueAndNew&server=" + serverUUID,
"graph?type=joinAddressPie&server=" + serverUUID,
"graph?type=joinAddressByDay&server=" + serverUUID,
"graph?type=serverCalendar&server=" + serverUUID,
"graph?type=punchCard&server=" + serverUUID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.djrapitops.plan.delivery.domain.DateObj;
import com.djrapitops.plan.delivery.domain.RetentionData;
import com.djrapitops.plan.delivery.domain.datatransfer.PlayerJoinAddresses;
import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto;
import com.djrapitops.plan.delivery.domain.mutators.PlayerKillMutator;
import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator;
Expand Down Expand Up @@ -160,14 +161,25 @@ public List<RetentionData> networkPlayerRetentionAsJSONMap() {
return db.query(PlayerRetentionQueries.fetchRetentionData());
}

public Map<UUID, String> playerJoinAddresses(ServerUUID serverUUID) {
public PlayerJoinAddresses playerJoinAddresses(ServerUUID serverUUID, boolean includeByPlayerMap) {
Database db = dbSystem.getDatabase();
return db.query(JoinAddressQueries.latestJoinAddressesOfPlayers(serverUUID));
if (includeByPlayerMap) {
Map<UUID, String> addresses = db.query(JoinAddressQueries.latestJoinAddressesOfPlayers(serverUUID));
return new PlayerJoinAddresses(
addresses.values().stream().distinct().sorted().collect(Collectors.toList()),
addresses
);
} else {
return new PlayerJoinAddresses(db.query(JoinAddressQueries.uniqueJoinAddresses(serverUUID)), null);
}
}

public Map<UUID, String> playerJoinAddresses() {
public PlayerJoinAddresses playerJoinAddresses(boolean includeByPlayerMap) {
Database db = dbSystem.getDatabase();
return db.query(JoinAddressQueries.latestJoinAddressesOfPlayers());
return new PlayerJoinAddresses(
db.query(JoinAddressQueries.uniqueJoinAddresses()),
includeByPlayerMap ? db.query(JoinAddressQueries.latestJoinAddressesOfPlayers()) : null
);
}

public List<Map<String, Object>> serverSessionsAsJSONMap(ServerUUID serverUUID) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import com.djrapitops.plan.delivery.rendering.json.graphs.line.PingGraph;
import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point;
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.Pie;
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.PieSlice;
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.WorldPie;
import com.djrapitops.plan.delivery.rendering.json.graphs.special.WorldMap;
import com.djrapitops.plan.delivery.rendering.json.graphs.stack.StackGraph;
Expand All @@ -57,7 +56,7 @@
import com.djrapitops.plan.storage.database.queries.objects.*;
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 @@ -457,34 +456,6 @@ public Map<String, Object> serverPreferencePieJSONAsMap() {
.build();
}

public Map<String, Object> playerHostnamePieJSONAsMap() {
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
Map<String, Integer> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.latestJoinAddresses());

translateUnknown(joinAddresses);

List<PieSlice> slices = graphs.pie().joinAddressPie(joinAddresses).getSlices();
slices.sort(new PieSliceComparator());
return Maps.builder(String.class, Object.class)
.put("colors", pieColors)
.put("slices", slices)
.build();
}

public Map<String, Object> playerHostnamePieJSONAsMap(ServerUUID serverUUID) {
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
Map<String, Integer> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.latestJoinAddresses(serverUUID));

translateUnknown(joinAddresses);

List<PieSlice> slices = graphs.pie().joinAddressPie(joinAddresses).getSlices();
slices.sort(new PieSliceComparator());
return Maps.builder(String.class, Object.class)
.put("colors", pieColors)
.put("slices", slices)
.build();
}

public void translateUnknown(Map<String, Integer> joinAddresses) {
Integer unknown = joinAddresses.get(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
if (unknown != null) {
Expand All @@ -493,16 +464,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

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,6 @@ public Pie serverPreferencePie(Map<String, Long> playtimeByServerName) {
return new ServerPreferencePie(playtimeByServerName);
}

public Pie joinAddressPie(Map<String, Integer> joinAddresses) {
return new JoinAddressPie(joinAddresses);
}

public WorldPie worldPie(WorldTimes worldTimes) {
WorldAliasSettings worldAliasSettings = config.getWorldAliasSettings();
Map<String, Long> playtimePerAlias = worldAliasSettings.getPlaytimePerAlias(worldTimes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ public enum DataID {
GRAPH_ACTIVITY,
GRAPH_PING,
GRAPH_SERVER_PIE,
GRAPH_HOSTNAME_PIE,
GRAPH_PUNCHCARD,
SERVER_OVERVIEW,
ONLINE_OVERVIEW,
Expand All @@ -54,12 +53,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
Loading

0 comments on commit f40e149

Please sign in to comment.