Skip to content

Commit cc7d36e

Browse files
authored
Implement dynamic session tabs (#4536)
* Session page redesign - Link session calendar and session list together - Add Tanstack react query - Rewrote /v1/sessions endpoint - Wrote logic for a generic filter that will allow easier data filtering in the future * Datapoint endpoint * Link player session components together * Load player world times based on selected filters * AFK time datapoints * Server occupied datapoint * Most active gamemode and world * Small fixes * Better date picker * Minor fixes - Corrected timezone offset calculations in `ServerSessionCalendarCard.tsx`. - Updated incorrect icons and labels in `SessionAccordion.jsx`. - Adjusted AFK time percentage calculation in `SessionQueries.java`. - Modified `ServerRecentSessionsCard.jsx` to remove redundant `isPlayer` prop. - Improved `DateInputField.tsx` by adding metadata usage and disabling auto-close on selection. * Fix timezone issues with date picker * Fix future date reading as 'Today' * Limit server session query to 10k sessions * Fix export of sessions endpoint * Implement datapoint endpoints export * Fix default datapoint permissions * Add ServerPie datapoint * Fix permission issues with datapoint-based graph endpoints * Fix java date formatter Future dates showing Today * Add missing license headers * Fix permission patch when no group has a permission * Fix Sql.nParameters usage when parameter list is empty * Create plan_servers_uuid_index * Use join to server table uuid check instead of inner query * Fix issues * Fix wrong exported resources * Caching implementation for /v1/datapoint and /v1/sessions * Remove Display_options.Sessions.Show_on_page * Fix etag issue * Fix javascript date formatter recent days * Fix session table export * Fix date picker buttons overflow on long month names * Locale loading for DatePicker Affects issues: - Close #2742 - Close #4485
1 parent 75e3588 commit cc7d36e

262 files changed

Lines changed: 5176 additions & 1106 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/request/URIQuery.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.HashMap;
2727
import java.util.Map;
2828
import java.util.Optional;
29+
import java.util.function.Function;
2930

3031
/**
3132
* Represents URI parameters described with {@code ?param=value&param2=value2} in the URL.
@@ -98,6 +99,10 @@ public Optional<String> get(String key) {
9899
return Optional.ofNullable(byKey.get(key));
99100
}
100101

102+
public <T> Optional<T> get(String key, Function<String, T> function) {
103+
return get(key).map(function);
104+
}
105+
101106
public String asString() {
102107
if (byKey.isEmpty()) return "";
103108

Plan/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ subprojects {
9494
junitVersion = "6.0.3"
9595
mockitoVersion = "5.23.0"
9696
seleniumVersion = "4.41.0"
97-
testContainersVersion = "1.21.3"
97+
testContainersVersion = "1.21.4"
9898
awaitilityVersion = "4.3.0"
9999
swaggerVersion = "2.2.45"
100100
guavaVersion = "33.5.0-jre"

Plan/bukkit/src/main/java/com/djrapitops/plan/PlanBukkitComponent.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.djrapitops.plan.addons.placeholderapi.BukkitPlaceholderRegistrar;
2020
import com.djrapitops.plan.commands.PlanCommand;
21+
import com.djrapitops.plan.delivery.rendering.json.datapoint.types.DatapointModule;
2122
import com.djrapitops.plan.gathering.ServerShutdownSave;
2223
import com.djrapitops.plan.modules.*;
2324
import com.djrapitops.plan.modules.bukkit.BukkitPlanModule;
@@ -44,6 +45,7 @@
4445
PlatformAbstractionLayerModule.class,
4546
FiltersModule.class,
4647
PlaceholderModule.class,
48+
DatapointModule.class,
4749

4850
ServerCommandModule.class,
4951
BukkitServerPropertiesModule.class,

Plan/bukkit/src/main/java/com/djrapitops/plan/modules/bukkit/BukkitSuperClassBindingModule.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,6 @@ public interface BukkitSuperClassBindingModule {
5959
ServerSensor<World> bindServerSensor(BukkitSensor sensor);
6060

6161
@Binds
62+
@SuppressWarnings("java:S1452")
6263
ServerSensor<?> bindGenericsServerSensor(ServerSensor<World> sensor);
6364
}

Plan/bungeecord/src/main/java/com/djrapitops/plan/PlanBungeeComponent.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.djrapitops.plan;
1818

1919
import com.djrapitops.plan.commands.PlanCommand;
20+
import com.djrapitops.plan.delivery.rendering.json.datapoint.types.DatapointModule;
2021
import com.djrapitops.plan.modules.*;
2122
import com.djrapitops.plan.modules.bungee.*;
2223
import com.djrapitops.plan.utilities.logging.ErrorLogger;
@@ -39,6 +40,7 @@
3940
PlatformAbstractionLayerModule.class,
4041
FiltersModule.class,
4142
PlaceholderModule.class,
43+
DatapointModule.class,
4244

4345
ProxySuperClassBindingModule.class,
4446
BungeeSuperClassBindingModule.class,

Plan/bungeecord/src/main/java/com/djrapitops/plan/modules/bungee/BungeeSuperClassBindingModule.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,6 @@ public interface BungeeSuperClassBindingModule {
4040
ListenerSystem bindListenerSystem(BungeeListenerSystem listenerSystem);
4141

4242
@Binds
43+
@SuppressWarnings("java:S1452")
4344
ServerSensor<?> bindServerSensor(BungeeSensor sensor);
4445
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* This file is part of Player Analytics (Plan).
3+
*
4+
* Plan is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License v3 as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* Plan is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public License
15+
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
package com.djrapitops.plan.delivery.domain;
18+
19+
import com.djrapitops.plan.delivery.rendering.json.datapoint.Datapoint;
20+
21+
/**
22+
* @author AuroraLS3
23+
*/
24+
public class OutOf {
25+
private final long value;
26+
private final long max;
27+
private final double percentage;
28+
private final Datapoint.FormatType formatType;
29+
30+
public OutOf(long value, long max, Datapoint.FormatType formatType) {
31+
this.value = value;
32+
this.max = max;
33+
this.formatType = formatType;
34+
this.percentage = max != 0L ? value * 1.0 / max : 0.0;
35+
}
36+
37+
public long getValue() {
38+
return value;
39+
}
40+
41+
public long getMax() {
42+
return max;
43+
}
44+
45+
public double getPercentage() {
46+
return percentage;
47+
}
48+
49+
public Datapoint.FormatType getFormatType() {
50+
return formatType;
51+
}
52+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* This file is part of Player Analytics (Plan).
3+
*
4+
* Plan is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License v3 as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* Plan is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public License
15+
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
package com.djrapitops.plan.delivery.domain;
18+
19+
import org.jspecify.annotations.Nullable;
20+
21+
import java.util.Map;
22+
23+
/**
24+
* @author AuroraLS3
25+
*/
26+
public class OutOfCategory {
27+
28+
private final Map<String, Long> values;
29+
private final long max;
30+
@Nullable
31+
private String category;
32+
@Nullable
33+
private Double percentage;
34+
35+
public OutOfCategory(Map<String, Long> values, long max) {
36+
this.values = values;
37+
this.max = max;
38+
long biggest = 0L;
39+
for (Map.Entry<String, Long> entry : values.entrySet()) {
40+
if (entry.getValue() > biggest) {
41+
category = entry.getKey();
42+
biggest = entry.getValue();
43+
percentage = max != 0L ? biggest / max : 0.0;
44+
}
45+
}
46+
}
47+
48+
public Map<String, Long> getValues() {
49+
return values;
50+
}
51+
52+
public long getMax() {
53+
return max;
54+
}
55+
56+
public String getCategory() {
57+
return category;
58+
}
59+
60+
public Double getPercentage() {
61+
return percentage;
62+
}
63+
}

Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/auth/WebPermission.java

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public enum WebPermission implements Supplier<String>, Lang {
4747
PAGE_NETWORK_SESSIONS_OVERVIEW("See Session insights"),
4848
PAGE_NETWORK_SESSIONS_WORLD_PIE("See World Pie graph"),
4949
PAGE_NETWORK_SESSIONS_SERVER_PIE("See Server Pie graph"),
50+
PAGE_NETWORK_SESSIONS_CALENDAR("See Network calendar"),
5051
PAGE_NETWORK_SESSIONS_LIST("See list of sessions"),
5152
PAGE_NETWORK_JOIN_ADDRESSES("See Join Addresses -tab"),
5253
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
@@ -83,6 +84,7 @@ public enum WebPermission implements Supplier<String>, Lang {
8384
PAGE_SERVER_SESSIONS("See Sessions tab"),
8485
PAGE_SERVER_SESSIONS_OVERVIEW("See Session insights"),
8586
PAGE_SERVER_SESSIONS_WORLD_PIE("See World Pie graph"),
87+
PAGE_SERVER_SESSIONS_CALENDAR("See Server calendar"),
8688
PAGE_SERVER_SESSIONS_LIST("See list of sessions"),
8789
PAGE_SERVER_JOIN_ADDRESSES("See Join Addresses -tab"),
8890
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
@@ -116,6 +118,31 @@ public enum WebPermission implements Supplier<String>, Lang {
116118
PAGE_PLAYER_SERVERS("See Servers -tab"),
117119
PAGE_PLAYER_PLUGINS("See Plugins -tabs"),
118120

121+
DATA("Access all data from /v1/dataPoint"),
122+
DATA_PLAYER("Access all /v1/dataPoint endpoint player data. Also needs ACCESS_PLAYER or ACCESS_PLAYER_SELF."),
123+
DATA_SERVER("Access all /v1/dataPoint endpoint server data"),
124+
DATA_NETWORK("Access all /v1/dataPoint endpoint network data"),
125+
DATA_PLAYER_PLAYTIME("See Playtime datapoint of players"),
126+
DATA_SERVER_PLAYTIME("See Playtime datapoint of servers"),
127+
DATA_NETWORK_PLAYTIME("See Playtime datapoint of network"),
128+
DATA_PLAYER_AFK_TIME("See AFK datapoint of players"),
129+
DATA_SERVER_AFK_TIME("See AFK datapoint of servers"),
130+
DATA_NETWORK_AFK_TIME("See AFK datapoint of network"),
131+
DATA_SERVER_SERVER_OCCUPIED("See Server occupied -datapoint of servers"),
132+
DATA_NETWORK_SERVER_OCCUPIED("See Server occupied -datapoint of network"),
133+
DATA_PLAYER_MOST_ACTIVE_GAME_MODE("See Most Active Gamemode -datapoint of players"),
134+
DATA_SERVER_MOST_ACTIVE_GAME_MODE("See Most Active Gamemode -datapoint of servers"),
135+
DATA_NETWORK_MOST_ACTIVE_GAME_MODE("See Most Active Gamemode -datapoint of network"),
136+
DATA_PLAYER_MOST_ACTIVE_WORLD("See Most Active World -datapoint of players"),
137+
DATA_SERVER_MOST_ACTIVE_WORLD("See Most Active World -datapoint of servers"),
138+
DATA_NETWORK_MOST_ACTIVE_WORLD("See Most Active World -datapoint of network"),
139+
DATA_PLAYER_WORLD_PIE("See World Pie -datapoint of players"),
140+
DATA_SERVER_WORLD_PIE("See World Pie -datapoint of servers"),
141+
DATA_NETWORK_WORLD_PIE("See World Pie -datapoint of network"),
142+
DATA_PLAYER_SERVER_PIE("See Server Pie -datapoint of players"),
143+
DATA_SERVER_SERVER_PIE("See Server Pie -datapoint of servers"),
144+
DATA_NETWORK_SERVER_PIE("See Server Pie -datapoint of network"),
145+
119146
ACCESS("Controls access to pages"),
120147
ACCESS_PLAYER("Allows accessing any /player pages"),
121148
ACCESS_PLAYER_SELF("Allows accessing own /player page"),
@@ -145,6 +172,25 @@ public enum WebPermission implements Supplier<String>, Lang {
145172
this.deprecated = deprecated;
146173
}
147174

175+
public static WebPermission[] nonDeprecatedValues() {
176+
return Arrays.stream(values())
177+
.filter(Predicate.not(WebPermission::isDeprecated))
178+
.toArray(WebPermission[]::new);
179+
}
180+
181+
public static Optional<WebPermission> findByPermission(String permission) {
182+
String name = StringUtils.upperCase(permission).replace('.', '_');
183+
try {
184+
return Optional.of(valueOf(name));
185+
} catch (IllegalArgumentException noSuchEnum) {
186+
return Optional.empty();
187+
}
188+
}
189+
190+
public static boolean isDeprecated(String permission) {
191+
return findByPermission(permission).map(WebPermission::isDeprecated).orElse(false);
192+
}
193+
148194
public String getPermission() {
149195
return StringUtils.lowerCase(name()).replace('_', '.');
150196
}
@@ -172,23 +218,4 @@ public String getKey() {
172218
public String getDefault() {
173219
return description;
174220
}
175-
176-
public static WebPermission[] nonDeprecatedValues() {
177-
return Arrays.stream(values())
178-
.filter(Predicate.not(WebPermission::isDeprecated))
179-
.toArray(WebPermission[]::new);
180-
}
181-
182-
public static Optional<WebPermission> findByPermission(String permission) {
183-
String name = StringUtils.upperCase(permission).replace('.', '_');
184-
try {
185-
return Optional.of(valueOf(name));
186-
} catch (IllegalArgumentException noSuchEnum) {
187-
return Optional.empty();
188-
}
189-
}
190-
191-
public static boolean isDeprecated(String permission) {
192-
return findByPermission(permission).map(WebPermission::isDeprecated).orElse(false);
193-
}
194221
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* This file is part of Player Analytics (Plan).
3+
*
4+
* Plan is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License v3 as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* Plan is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public License
15+
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
package com.djrapitops.plan.delivery.domain.datatransfer;
18+
19+
import com.djrapitops.plan.delivery.web.resolver.request.URIQuery;
20+
import com.djrapitops.plan.identification.ServerUUID;
21+
import com.djrapitops.plan.utilities.dev.Untrusted;
22+
import com.djrapitops.plan.utilities.java.CatchingParsers;
23+
import org.apache.commons.lang3.StringUtils;
24+
import org.jspecify.annotations.Nullable;
25+
26+
import java.util.*;
27+
import java.util.stream.Collectors;
28+
29+
/**
30+
* Generic filter that can be constructed from URIQuery.
31+
*
32+
* @author AuroraLS3
33+
*/
34+
public class GenericFilter {
35+
36+
private final @Nullable Long after;
37+
private final @Nullable Long before;
38+
private final @Untrusted List<String> serverIdentifiers;
39+
private final @Nullable UUID playerUUID;
40+
private List<ServerUUID> serverUUIDs;
41+
42+
public GenericFilter(@Untrusted URIQuery query) {
43+
after = query.get("after", CatchingParsers::parseLong)
44+
.orElseGet(() -> query.get("afterMillisAgo", CatchingParsers::parseLong)
45+
.map(value -> System.currentTimeMillis() - value)
46+
.orElse(null));
47+
before = query.get("before", CatchingParsers::parseLong)
48+
.orElseGet(() -> query.get("beforeMillisAgo", CatchingParsers::parseLong)
49+
.map(value -> System.currentTimeMillis() - value)
50+
.orElse(null));
51+
serverIdentifiers = query.get("server", s -> StringUtils.split(s, ','))
52+
.map(Arrays::asList)
53+
.orElse(List.of());
54+
serverUUIDs = serverIdentifiers.stream()
55+
.map(CatchingParsers::parseServerUUID)
56+
.filter(Objects::nonNull)
57+
.collect(Collectors.toList());
58+
playerUUID = query.get("player", CatchingParsers::parsePlayerUUID).orElse(null);
59+
}
60+
61+
public static GenericFilter of(@Untrusted URIQuery query) {
62+
return new GenericFilter(query);
63+
}
64+
65+
public Long getAfter() {
66+
return after != null ? after : 0L;
67+
}
68+
69+
public Long getBefore() {
70+
return before != null ? before : Long.MAX_VALUE;
71+
}
72+
73+
public List<ServerUUID> getServerUUIDs() {
74+
return serverUUIDs;
75+
}
76+
77+
public void setServerUUIDs(List<ServerUUID> serverUUIDs) {
78+
this.serverUUIDs = serverUUIDs;
79+
}
80+
81+
public boolean contains(ServerUUID serverUUID) {
82+
return serverUUIDs.isEmpty() || serverUUIDs.contains(serverUUID);
83+
}
84+
85+
public @Untrusted List<String> getServerIdentifiers() {
86+
return serverIdentifiers;
87+
}
88+
89+
public boolean didAllServerIdentifiersParse() {
90+
return serverIdentifiers.size() == serverUUIDs.size();
91+
}
92+
93+
public Optional<UUID> getPlayerUUID() {
94+
return Optional.ofNullable(playerUUID);
95+
}
96+
}

0 commit comments

Comments
 (0)