Query page view server selector (#2117)

Affects issues:
- Close #1829
This commit is contained in:
Aurora Lahtela 2021-10-09 13:17:12 +03:00 committed by GitHub
parent fd84341a77
commit d06c753be1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 698 additions and 185 deletions

View File

@ -0,0 +1,73 @@
/*
* 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 com.djrapitops.plan.storage.database.queries.filter.Filter;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
/**
* Represents a query filter.
*
* @see Filter
* @see com.djrapitops.plan.modules.FiltersModule
*/
public class FilterDto implements Comparable<FilterDto> {
private final String kind;
private final Map<String, Object> options;
private final String[] expectedParameters;
public FilterDto(String kind, Filter filter) {
this.kind = kind;
this.options = filter.getOptions();
this.expectedParameters = filter.getExpectedParameters();
}
public String getKind() {
return kind;
}
public Map<String, Object> getOptions() {
return options;
}
public String[] getExpectedParameters() {
return expectedParameters;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FilterDto that = (FilterDto) o;
return Objects.equals(kind, that.kind) && Objects.equals(options, that.options) && Arrays.equals(expectedParameters, that.expectedParameters);
}
@Override
public int hashCode() {
int result = Objects.hash(kind, options);
result = 31 * result + Arrays.hashCode(expectedParameters);
return result;
}
@Override
public int compareTo(FilterDto o) {
return String.CASE_INSENSITIVE_ORDER.compare(this.kind, o.kind);
}
}

View File

@ -14,8 +14,9 @@
* 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.storage.database.queries.filter;
package com.djrapitops.plan.delivery.domain.datatransfer;
import com.djrapitops.plan.storage.database.queries.filter.Filter;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
@ -27,18 +28,18 @@ import java.util.*;
*
* @author AuroraLS3
*/
public class SpecifiedFilterInformation {
public class InputFilterDto {
private final String kind;
private final Map<String, String> parameters;
public SpecifiedFilterInformation(String kind, Map<String, String> parameters) {
public InputFilterDto(String kind, Map<String, String> parameters) {
this.kind = kind;
this.parameters = parameters;
}
public static List<SpecifiedFilterInformation> parse(String json) throws IOException {
return new Gson().getAdapter(new TypeToken<List<SpecifiedFilterInformation>>() {}).fromJson(json);
public static List<InputFilterDto> parse(String json, Gson gson) throws IOException {
return gson.getAdapter(new TypeToken<List<InputFilterDto>>() {}).fromJson(json);
}
public String getKind() {

View File

@ -0,0 +1,60 @@
/*
* 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.Objects;
public class InputQueryDto {
public final List<InputFilterDto> filters;
private final ViewDto view;
public InputQueryDto(ViewDto view, List<InputFilterDto> filters) {
this.view = view;
this.filters = filters;
}
public ViewDto getView() {
return view;
}
public List<InputFilterDto> getFilters() {
return filters;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InputQueryDto that = (InputQueryDto) o;
return Objects.equals(getView(), that.getView()) && Objects.equals(getFilters(), that.getFilters());
}
@Override
public int hashCode() {
return Objects.hash(getView(), getFilters());
}
@Override
public String toString() {
return "InputQueryDto{" +
"view=" + view +
", filters=" + filters +
'}';
}
}

View File

@ -0,0 +1,60 @@
/*
* 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 com.djrapitops.plan.identification.Server;
/**
* Represents outgoing server information json.
*/
public class ServerDto {
private final String serverUUID;
private final String serverName;
private final boolean proxy;
public ServerDto(String serverUUID, String serverName, boolean proxy) {
this.serverUUID = serverUUID;
this.serverName = serverName;
this.proxy = proxy;
}
public static ServerDto fromServer(Server server) {
return new ServerDto(server.getUuid().toString(), server.getIdentifiableName(), server.isProxy());
}
public String getServerUUID() {
return serverUUID;
}
public String getServerName() {
return serverName;
}
public boolean isProxy() {
return proxy;
}
@Override
public String toString() {
return "ServerDto{" +
"serverUUID='" + serverUUID + '\'' +
", serverName='" + serverName + '\'' +
", proxy=" + proxy +
'}';
}
}

View File

@ -0,0 +1,85 @@
/*
* 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 com.djrapitops.plan.delivery.formatting.Formatter;
import com.djrapitops.plan.delivery.formatting.Formatters;
import com.djrapitops.plan.identification.ServerUUID;
import org.apache.commons.lang3.StringUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* Represents query page view that the user wants to see data for.
*/
public class ViewDto {
private static final String DATE_PATTERN = "dd/MM/yyyy kk:mm";
private final String afterDate;
private final String afterTime;
private final String beforeDate;
private final String beforeTime;
private final List<ServerDto> servers;
public ViewDto(Formatters formatters, List<ServerDto> servers) {
this.servers = servers;
long now = System.currentTimeMillis();
long monthAgo = now - TimeUnit.DAYS.toMillis(30);
Formatter<Long> formatter = formatters.javascriptDateFormatterLong();
String[] after = StringUtils.split(formatter.apply(monthAgo), " ");
String[] before = StringUtils.split(formatter.apply(now), " ");
this.afterDate = after[0];
this.afterTime = after[1];
this.beforeDate = before[0];
this.beforeTime = before[1];
}
public long getAfterEpochMs() throws ParseException {
return new SimpleDateFormat(DATE_PATTERN).parse(afterDate + " " + afterTime).getTime();
}
public long getBeforeEpochMs() throws ParseException {
return new SimpleDateFormat(DATE_PATTERN).parse(beforeDate + " " + beforeTime).getTime();
}
public List<ServerUUID> getServerUUIDs() {
return servers.stream()
.map(ServerDto::getServerUUID)
.map(ServerUUID::fromString)
.collect(Collectors.toList());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ViewDto viewDto = (ViewDto) o;
return Objects.equals(afterDate, viewDto.afterDate) && Objects.equals(afterTime, viewDto.afterTime) && Objects.equals(beforeDate, viewDto.beforeDate) && Objects.equals(beforeTime, viewDto.beforeTime) && Objects.equals(servers, viewDto.servers);
}
@Override
public int hashCode() {
return Objects.hash(afterDate, afterTime, beforeDate, beforeTime, servers);
}
}

View File

@ -0,0 +1,4 @@
/**
* Data transfer objects or DTOs, which represent the outgoing or incoming serialized json.
*/
package com.djrapitops.plan.delivery.domain.datatransfer;

View File

@ -17,6 +17,7 @@
package com.djrapitops.plan.delivery.rendering.json;
import com.djrapitops.plan.delivery.domain.DateObj;
import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto;
import com.djrapitops.plan.delivery.domain.mutators.PlayerKillMutator;
import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator;
import com.djrapitops.plan.delivery.domain.mutators.TPSMutator;
@ -266,16 +267,10 @@ public class JSONFactory {
return tableEntries;
}
public Map<String, Object> listServers() {
public Map<String, List<ServerDto>> listServers() {
Collection<Server> servers = dbSystem.getDatabase().query(ServerQueries.fetchPlanServerInformationCollection());
return Maps.builder(String.class, Object.class)
.put("servers", servers.stream()
.map(server -> Maps.builder(String.class, Object.class)
.put("serverUUID", server.getUuid().toString())
.put("serverName", server.getIdentifiableName())
.put("proxy", server.isProxy())
.build())
.collect(Collectors.toList()))
.build();
return Collections.singletonMap("servers", servers.stream()
.map(ServerDto::fromServer)
.collect(Collectors.toList()));
}
}

View File

@ -89,6 +89,7 @@ public class WebAssetVersionCheckTask extends TaskSystem.Task {
if (!outdated.isEmpty()) {
logger.warn("You have customized files which are out of date due to recent updates!");
logger.warn("Plan may not work properly until these files are updated to include the new modifications.");
logger.warn("See https://github.com/plan-player-analytics/Plan/commits/html to compare changes");
}
for (AssetInfo asset : outdated) {
logger.warn(String.format("- %s was modified %s, but the plugin contains a version from %s",

View File

@ -18,6 +18,10 @@ package com.djrapitops.plan.delivery.webserver;
import com.djrapitops.plan.delivery.web.resolver.request.Request;
import com.djrapitops.plan.delivery.web.resolver.request.URIQuery;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.nio.charset.StandardCharsets;
public class RequestBodyConverter {
@ -35,9 +39,17 @@ public class RequestBodyConverter {
"POST".equalsIgnoreCase(request.getMethod()) &&
"application/x-www-form-urlencoded".equalsIgnoreCase(request.getHeader("Content-type").orElse(""))
) {
return new URIQuery(new String(request.getRequestBody()));
return new URIQuery(new String(request.getRequestBody(), StandardCharsets.UTF_8));
} else {
return new URIQuery("");
}
}
public static <T> T bodyJson(Request request, Gson gson, Class<T> ofType) {
return gson.fromJson(new String(request.getRequestBody(), StandardCharsets.UTF_8), ofType);
}
public static <T> T bodyJson(Request request, Gson gson, TypeToken<T> ofType) {
return gson.fromJson(new String(request.getRequestBody(), StandardCharsets.UTF_8), ofType.getType());
}
}

View File

@ -17,8 +17,10 @@
package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.DateObj;
import com.djrapitops.plan.delivery.formatting.Formatter;
import com.djrapitops.plan.delivery.domain.datatransfer.FilterDto;
import com.djrapitops.plan.delivery.domain.datatransfer.ViewDto;
import com.djrapitops.plan.delivery.formatting.Formatters;
import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
import com.djrapitops.plan.delivery.rendering.json.graphs.Graphs;
import com.djrapitops.plan.delivery.rendering.json.graphs.line.LineGraph;
import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point;
@ -36,7 +38,6 @@ import com.djrapitops.plan.storage.database.queries.objects.TPSQueries;
import com.djrapitops.plan.utilities.java.Lists;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import org.apache.commons.lang3.StringUtils;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -50,6 +51,7 @@ public class FiltersJSONResolver implements Resolver {
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final QueryFilters filters;
private final JSONFactory jsonFactory;
private final Graphs graphs;
private final Formatters formatters;
private final ErrorLogger errorLogger;
@ -59,6 +61,7 @@ public class FiltersJSONResolver implements Resolver {
ServerInfo serverInfo,
DBSystem dbSystem,
QueryFilters filters,
JSONFactory jsonFactory,
Graphs graphs,
Formatters formatters,
ErrorLogger errorLogger
@ -66,6 +69,7 @@ public class FiltersJSONResolver implements Resolver {
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.filters = filters;
this.jsonFactory = jsonFactory;
this.graphs = graphs;
this.formatters = formatters;
this.errorLogger = errorLogger;
@ -85,9 +89,9 @@ public class FiltersJSONResolver implements Resolver {
private Response getResponse() {
return Response.builder()
.setMimeType(MimeType.JSON)
.setJSONContent(new FilterResponseJSON(
.setJSONContent(new FilterResponseDto(
filters.getFilters(),
new ViewJSON(formatters),
new ViewDto(formatters, jsonFactory.listServers().get("servers")),
fetchViewGraphPoints()
)).build();
}
@ -111,17 +115,17 @@ public class FiltersJSONResolver implements Resolver {
/**
* JSON serialization class.
*/
class FilterResponseJSON {
final List<FilterJSON> filters;
final ViewJSON view;
class FilterResponseDto {
final List<FilterDto> filters;
final ViewDto view;
final List<Double[]> viewPoints;
public FilterResponseJSON(Map<String, Filter> filtersByKind, ViewJSON view, List<Double[]> viewPoints) {
public FilterResponseDto(Map<String, Filter> filtersByKind, ViewDto view, List<Double[]> viewPoints) {
this.viewPoints = viewPoints;
this.filters = new ArrayList<>();
for (Map.Entry<String, Filter> entry : filtersByKind.entrySet()) {
try {
filters.add(new FilterJSON(entry.getKey(), entry.getValue()));
filters.add(new FilterDto(entry.getKey(), entry.getValue()));
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder()
.whatToDo("Report this, filter '" + entry.getKey() + "' has implementation error.")
@ -133,63 +137,4 @@ public class FiltersJSONResolver implements Resolver {
this.view = view;
}
}
/**
* JSON serialization class.
*/
static class FilterJSON implements Comparable<FilterJSON> {
final String kind;
final Map<String, Object> options;
final String[] expectedParameters;
public FilterJSON(String kind, Filter filter) {
this.kind = kind;
this.options = filter.getOptions();
this.expectedParameters = filter.getExpectedParameters();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FilterJSON that = (FilterJSON) o;
return Objects.equals(kind, that.kind) && Objects.equals(options, that.options) && Arrays.equals(expectedParameters, that.expectedParameters);
}
@Override
public int hashCode() {
int result = Objects.hash(kind, options);
result = 31 * result + Arrays.hashCode(expectedParameters);
return result;
}
@Override
public int compareTo(FilterJSON o) {
return String.CASE_INSENSITIVE_ORDER.compare(this.kind, o.kind);
}
}
/**
* JSON serialization class.
*/
static class ViewJSON {
final String afterDate;
final String afterTime;
final String beforeDate;
final String beforeTime;
public ViewJSON(Formatters formatters) {
long now = System.currentTimeMillis();
long monthAgo = now - TimeUnit.DAYS.toMillis(30);
Formatter<Long> formatter = formatters.javascriptDateFormatterLong();
String[] after = StringUtils.split(formatter.apply(monthAgo), " ");
String[] before = StringUtils.split(formatter.apply(now), " ");
this.afterDate = after[0];
this.afterTime = after[1];
this.beforeDate = before[0];
this.beforeTime = before[1];
}
}
}

View File

@ -57,13 +57,15 @@ public class NetworkPerformanceJSONResolver implements Resolver {
private final Formatter<Long> timeAmount;
private final Formatter<Double> percentage;
private final Formatter<Double> byteSize;
private final Gson gson;
@Inject
public NetworkPerformanceJSONResolver(
PlanConfig config,
Locale locale,
DBSystem dbSystem,
Formatters formatters
Formatters formatters,
Gson gson
) {
this.config = config;
this.locale = locale;
@ -73,6 +75,7 @@ public class NetworkPerformanceJSONResolver implements Resolver {
percentage = formatters.percentage();
timeAmount = formatters.timeAmount();
byteSize = formatters.byteSize();
this.gson = gson;
}
@Override
@ -93,7 +96,7 @@ public class NetworkPerformanceJSONResolver implements Resolver {
}
private List<UUID> getUUIDList(String jsonString) {
return new Gson().fromJson(jsonString, new TypeToken<List<UUID>>() {}.getType());
return gson.fromJson(jsonString, new TypeToken<List<UUID>>() {}.getType());
}
public Map<String, Object> createJSONAsMap(Collection<ServerUUID> serverUUIDs) {

View File

@ -17,6 +17,9 @@
package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.DateMap;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.delivery.domain.datatransfer.InputQueryDto;
import com.djrapitops.plan.delivery.domain.datatransfer.ViewDto;
import com.djrapitops.plan.delivery.formatting.Formatter;
import com.djrapitops.plan.delivery.formatting.Formatters;
import com.djrapitops.plan.delivery.rendering.json.PlayersTableJSONCreator;
@ -27,9 +30,11 @@ import com.djrapitops.plan.delivery.web.resolver.Response;
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
import com.djrapitops.plan.delivery.web.resolver.request.Request;
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
import com.djrapitops.plan.delivery.webserver.RequestBodyConverter;
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionQueryResultTableDataQuery;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DisplaySettings;
import com.djrapitops.plan.settings.config.paths.TimeSettings;
@ -39,7 +44,6 @@ import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.queries.analysis.NetworkActivityIndexQueries;
import com.djrapitops.plan.storage.database.queries.filter.Filter;
import com.djrapitops.plan.storage.database.queries.filter.QueryFilters;
import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation;
import com.djrapitops.plan.storage.database.queries.objects.GeoInfoQueries;
import com.djrapitops.plan.storage.database.queries.objects.SessionQueries;
import com.djrapitops.plan.storage.database.queries.objects.playertable.QueryTablePlayersQuery;
@ -52,7 +56,6 @@ import javax.inject.Singleton;
import java.io.IOException;
import java.net.URLDecoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
@Singleton
@ -67,6 +70,7 @@ public class QueryJSONResolver implements Resolver {
private final GraphJSONCreator graphJSONCreator;
private final Locale locale;
private final Formatters formatters;
private final Gson gson;
@Inject
public QueryJSONResolver(
@ -76,7 +80,8 @@ public class QueryJSONResolver implements Resolver {
ServerInfo serverInfo, JSONStorage jsonStorage,
GraphJSONCreator graphJSONCreator,
Locale locale,
Formatters formatters
Formatters formatters,
Gson gson
) {
this.filters = filters;
this.config = config;
@ -86,6 +91,7 @@ public class QueryJSONResolver implements Resolver {
this.graphJSONCreator = graphJSONCreator;
this.locale = locale;
this.formatters = formatters;
this.gson = gson;
}
@Override
@ -103,17 +109,33 @@ public class QueryJSONResolver implements Resolver {
Optional<Response> cachedResult = checkForCachedResult(request);
if (cachedResult.isPresent()) return cachedResult.get();
String q = request.getQuery().get("q").orElseThrow(() -> new BadRequestException("'q' parameter not set (expecting json array)"));
String view = request.getQuery().get("view").orElseThrow(() -> new BadRequestException("'view' parameter not set (expecting json object {afterDate, afterTime, beforeDate, beforeTime})"));
InputQueryDto inputQuery = parseInputQuery(request);
List<InputFilterDto> queries = inputQuery.getFilters();
try {
String query = URLDecoder.decode(q, "UTF-8");
List<SpecifiedFilterInformation> queries = SpecifiedFilterInformation.parse(query);
Filter.Result result = filters.apply(queries);
List<Filter.ResultPath> resultPath = result.getInverseResultPath();
Collections.reverse(resultPath);
return buildAndStoreResponse(view, result, resultPath);
return buildAndStoreResponse(inputQuery.getView(), result, resultPath);
}
private InputQueryDto parseInputQuery(Request request) {
if (request.getRequestBody().length == 0) {
return parseInputQueryFromQueryParams(request);
} else {
return RequestBodyConverter.bodyJson(request, gson, InputQueryDto.class);
}
}
private InputQueryDto parseInputQueryFromQueryParams(Request request) {
String q = request.getQuery().get("q").orElseThrow(() -> new BadRequestException("'q' parameter not set (expecting json array)"));
try {
String query = URLDecoder.decode(q, "UTF-8");
List<InputFilterDto> queryFilters = InputFilterDto.parse(query, gson);
ViewDto view = request.getQuery().get("view")
.map(viewJson -> gson.fromJson(viewJson, ViewDto.class))
.orElseThrow(() -> new BadRequestException("'view' parameter not set (expecting json object {afterDate, afterTime, beforeDate, beforeTime})"));
return new InputQueryDto(view, queryFilters);
} catch (IOException e) {
throw new BadRequestException("Failed to decode json: '" + q + "', " + e.getMessage());
}
@ -132,12 +154,12 @@ public class QueryJSONResolver implements Resolver {
}
}
private Response buildAndStoreResponse(String view, Filter.Result result, List<Filter.ResultPath> resultPath) {
private Response buildAndStoreResponse(ViewDto view, Filter.Result result, List<Filter.ResultPath> resultPath) {
try {
long timestamp = System.currentTimeMillis();
Map<String, Object> json = Maps.builder(String.class, Object.class)
.put("path", resultPath)
.put("view", new Gson().fromJson(view, FiltersJSONResolver.ViewJSON.class))
.put("view", view)
.put("timestamp", timestamp)
.build();
if (!result.isEmpty()) {
@ -155,23 +177,22 @@ public class QueryJSONResolver implements Resolver {
}
}
private Map<String, Object> getDataFor(Set<UUID> playerUUIDs, String view) throws ParseException {
FiltersJSONResolver.ViewJSON viewJSON = new Gson().fromJson(view, FiltersJSONResolver.ViewJSON.class);
SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy kk:mm");
long after = dateFormat.parse(viewJSON.afterDate + " " + viewJSON.afterTime).getTime();
long before = dateFormat.parse(viewJSON.beforeDate + " " + viewJSON.beforeTime).getTime();
private Map<String, Object> getDataFor(Set<UUID> playerUUIDs, ViewDto view) throws ParseException {
long after = view.getAfterEpochMs();
long before = view.getBeforeEpochMs();
List<ServerUUID> serverUUIDs = view.getServerUUIDs();
return Maps.builder(String.class, Object.class)
.put("players", getPlayersTableData(playerUUIDs, after, before))
.put("activity", getActivityGraphData(playerUUIDs, after, before))
.put("players", getPlayersTableData(playerUUIDs, serverUUIDs, after, before))
.put("activity", getActivityGraphData(playerUUIDs, serverUUIDs, after, before))
.put("geolocation", getGeolocationData(playerUUIDs))
.put("sessions", getSessionSummaryData(playerUUIDs, after, before))
.put("sessions", getSessionSummaryData(playerUUIDs, serverUUIDs, after, before))
.build();
}
private Map<String, String> getSessionSummaryData(Set<UUID> playerUUIDs, long after, long before) {
private Map<String, String> getSessionSummaryData(Set<UUID> playerUUIDs, List<ServerUUID> serverUUIDs, long after, long before) {
Database database = dbSystem.getDatabase();
Map<String, Long> summary = database.query(SessionQueries.summaryOfPlayers(playerUUIDs, after, before));
Map<String, Long> summary = database.query(SessionQueries.summaryOfPlayers(playerUUIDs, serverUUIDs, after, before));
Map<String, String> formattedSummary = new HashMap<>();
Formatter<Long> timeAmount = formatters.timeAmount();
for (Map.Entry<String, Long> entry : summary.entrySet()) {
@ -189,7 +210,7 @@ public class QueryJSONResolver implements Resolver {
);
}
private Map<String, Object> getActivityGraphData(Set<UUID> playerUUIDs, long after, long before) {
private Map<String, Object> getActivityGraphData(Set<UUID> playerUUIDs, List<ServerUUID> serverUUIDs, long after, long before) {
Database database = dbSystem.getDatabase();
Long threshold = config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD);
@ -198,16 +219,16 @@ public class QueryJSONResolver implements Resolver {
DateMap<Map<String, Integer>> activityData = new DateMap<>();
for (long time = before; time >= stopDate; time -= TimeAmount.WEEK.toMillis(1L)) {
activityData.put(time, database.query(NetworkActivityIndexQueries.fetchActivityIndexGroupingsOn(time, threshold, playerUUIDs)));
activityData.put(time, database.query(NetworkActivityIndexQueries.fetchActivityIndexGroupingsOn(time, threshold, playerUUIDs, serverUUIDs)));
}
return graphJSONCreator.createActivityGraphJSON(activityData);
}
private Map<String, Object> getPlayersTableData(Set<UUID> playerUUIDs, long after, long before) {
private Map<String, Object> getPlayersTableData(Set<UUID> playerUUIDs, List<ServerUUID> serverUUIDs, long after, long before) {
Database database = dbSystem.getDatabase();
return new PlayersTableJSONCreator(
database.query(new QueryTablePlayersQuery(playerUUIDs, after, before, config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD))),
database.query(new QueryTablePlayersQuery(playerUUIDs, serverUUIDs, after, before, config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD))),
database.query(new ExtensionQueryResultTableDataQuery(serverInfo.getServerUUID(), playerUUIDs)),
config.get(DisplaySettings.OPEN_PLAYER_LINKS_IN_NEW_TAB),
formatters, locale

View File

@ -57,4 +57,8 @@ public interface FiltersModule {
@IntoSet
Filter filter8(PluginBooleanGroupFilter filter);
@Binds
@IntoSet
Filter filter9(PlayedOnServerFilter filter);
}

View File

@ -29,6 +29,7 @@ import com.djrapitops.plan.settings.locale.LocaleSystem;
import com.djrapitops.plan.storage.file.JarResource;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import com.djrapitops.plan.utilities.logging.PluginErrorLogger;
import com.google.gson.Gson;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
@ -49,6 +50,12 @@ import java.util.function.Predicate;
@Module
public class SystemObjectProvidingModule {
@Provides
@Singleton
Gson provideGson() {
return new Gson();
}
@Provides
@ElementsIntoSet
Set<Importer> emptyImporterSet() {

View File

@ -17,6 +17,7 @@
package com.djrapitops.plan.storage.database.queries.analysis;
import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.queries.Query;
import com.djrapitops.plan.storage.database.queries.QueryStatement;
import com.djrapitops.plan.storage.database.sql.tables.SessionsTable;
@ -74,6 +75,10 @@ public class NetworkActivityIndexQueries {
}
public static String selectActivityIndexSQL() {
return selectActivityIndexSQL(Collections.emptyList());
}
public static String selectActivityIndexSQL(Collection<ServerUUID> onServers) {
String selectActivePlaytimeSQL = SELECT +
"ux." + UsersTable.USER_UUID + ",COALESCE(active_playtime,0) AS active_playtime" +
FROM + UsersTable.TABLE_NAME + " ux" +
@ -82,6 +87,7 @@ public class NetworkActivityIndexQueries {
FROM + SessionsTable.TABLE_NAME +
WHERE + SessionsTable.SESSION_END + ">=?" +
AND + SessionsTable.SESSION_START + "<=?" +
(onServers.isEmpty() ? "" : AND + SessionsTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(onServers, "','") + "')") +
GROUP_BY + SessionsTable.USER_UUID +
") sx on sx.uuid=ux.uuid";
@ -163,8 +169,8 @@ public class NetworkActivityIndexQueries {
};
}
public static Query<Map<String, Integer>> fetchActivityIndexGroupingsOn(long date, long threshold, Collection<UUID> playerUUIDs) {
String selectActivityIndex = selectActivityIndexSQL();
public static Query<Map<String, Integer>> fetchActivityIndexGroupingsOn(long date, long threshold, Collection<UUID> playerUUIDs, List<ServerUUID> serverUUIDs) {
String selectActivityIndex = selectActivityIndexSQL(serverUUIDs);
String selectIndexes = SELECT + "activity_index" +
FROM + UsersTable.TABLE_NAME + " u" +

View File

@ -16,6 +16,8 @@
*/
package com.djrapitops.plan.storage.database.queries.filter;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import java.util.*;
/**
@ -40,9 +42,9 @@ public interface Filter {
* @return Set of UUIDs this filter applies to
* @throws IllegalArgumentException If the arguments are not valid.
*/
Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query);
Set<UUID> getMatchingUUIDs(InputFilterDto query);
default Result apply(SpecifiedFilterInformation query) {
default Result apply(InputFilterDto query) {
try {
return new Result(null, getKind(), getMatchingUUIDs(query));
} catch (CompleteSetException allMatch) {
@ -64,7 +66,7 @@ public interface Filter {
this.currentUUIDs = currentUUIDs;
}
public Result apply(Filter filter, SpecifiedFilterInformation query) {
public Result apply(Filter filter, InputFilterDto query) {
try {
Set<UUID> got = filter.getMatchingUUIDs(query);
currentUUIDs.retainAll(got);

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.storage.database.queries.filter;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.filter.filters.AllPlayersFilter;
@ -81,25 +82,25 @@ public class QueryFilters {
* @return the result object or null if none of the filterQueries could be applied.
* @throws BadRequestException If the request kind is not supported or if filter was given bad options.
*/
public Filter.Result apply(List<SpecifiedFilterInformation> filterQueries) {
public Filter.Result apply(List<InputFilterDto> filterQueries) {
prepareFilters();
Filter.Result current = null;
if (filterQueries.isEmpty()) return allPlayersFilter.apply(null);
for (SpecifiedFilterInformation specifiedFilterInformation : filterQueries) {
current = apply(current, specifiedFilterInformation);
for (InputFilterDto inputFilterDto : filterQueries) {
current = apply(current, inputFilterDto);
if (current != null && current.isEmpty()) break;
}
return current;
}
private Filter.Result apply(Filter.Result current, SpecifiedFilterInformation specifiedFilterInformation) {
String kind = specifiedFilterInformation.getKind();
private Filter.Result apply(Filter.Result current, InputFilterDto inputFilterDto) {
String kind = inputFilterDto.getKind();
Filter filter = getFilter(kind).orElseThrow(() -> new BadRequestException("Filter kind not supported: '" + kind + "'"));
return getResult(current, filter, specifiedFilterInformation);
return getResult(current, filter, inputFilterDto);
}
private Filter.Result getResult(Filter.Result current, Filter filter, SpecifiedFilterInformation query) {
private Filter.Result getResult(Filter.Result current, Filter filter, InputFilterDto query) {
try {
return current == null ? filter.apply(query) : current.apply(filter, query);
} catch (IllegalArgumentException badOptions) {

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.TimeSettings;
@ -23,7 +24,6 @@ import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.analysis.NetworkActivityIndexQueries;
import com.djrapitops.plan.storage.database.queries.filter.CompleteSetException;
import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -63,7 +63,7 @@ public class ActivityIndexFilter extends MultiOptionFilter {
}
@Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) {
public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
List<String> selected = getSelected(query);
String[] options = getOptionsArray();

View File

@ -16,9 +16,9 @@
*/
package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.filter.Filter;
import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation;
import com.djrapitops.plan.storage.database.queries.objects.UserIdentifierQueries;
import javax.inject.Inject;
@ -52,7 +52,7 @@ public class AllPlayersFilter implements Filter {
}
@Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) {
public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
return dbSystem.getDatabase().query(UserIdentifierQueries.fetchAllPlayerUUIDs());
}
}

View File

@ -16,11 +16,11 @@
*/
package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.settings.locale.lang.FilterLang;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.filter.CompleteSetException;
import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation;
import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries;
import javax.inject.Inject;
@ -57,7 +57,7 @@ public class BannedFilter extends MultiOptionFilter {
}
@Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) {
public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
List<String> selected = getSelected(query);
Set<UUID> uuids = new HashSet<>();
String[] options = getOptionsArray();

View File

@ -16,10 +16,10 @@
*/
package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.filter.Filter;
import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation;
import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries;
import com.djrapitops.plan.utilities.java.Maps;
import org.apache.commons.lang3.StringUtils;
@ -61,15 +61,15 @@ public abstract class DateRangeFilter implements Filter {
.build();
}
protected long getAfter(SpecifiedFilterInformation query) {
protected long getAfter(InputFilterDto query) {
return getTime(query, "afterDate", "afterTime");
}
protected long getBefore(SpecifiedFilterInformation query) {
protected long getBefore(InputFilterDto query) {
return getTime(query, "beforeDate", "beforeTime");
}
private long getTime(SpecifiedFilterInformation query, String dateKey, String timeKey) {
private long getTime(InputFilterDto query, String dateKey, String timeKey) {
String date = query.get(dateKey).orElseThrow(() -> new BadRequestException("'" + dateKey + "' not specified in parameters for " + getKind()));
String time = query.get(timeKey).orElseThrow(() -> new BadRequestException("'" + timeKey + "' not specified in parameters for " + getKind()));

View File

@ -16,8 +16,8 @@
*/
package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation;
import com.djrapitops.plan.storage.database.queries.objects.GeoInfoQueries;
import javax.inject.Inject;
@ -47,7 +47,7 @@ public class GeolocationsFilter extends MultiOptionFilter {
}
@Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) {
public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
return dbSystem.getDatabase().query(GeoInfoQueries.uuidsOfPlayersWithGeolocations(getSelected(query)));
}
}

View File

@ -16,8 +16,8 @@
*/
package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation;
import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries;
import javax.inject.Inject;
@ -47,7 +47,7 @@ public class JoinAddressFilter extends MultiOptionFilter {
}
@Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) {
public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
return dbSystem.getDatabase().query(UserInfoQueries.uuidsOfPlayersWithJoinAddresses(getSelected(query)));
}
}

View File

@ -16,9 +16,9 @@
*/
package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.storage.database.queries.filter.CompleteSetException;
import com.djrapitops.plan.storage.database.queries.filter.Filter;
import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
@ -31,7 +31,7 @@ public abstract class MultiOptionFilter implements Filter {
return new String[]{"selected"};
}
protected List<String> getSelected(SpecifiedFilterInformation query) {
protected List<String> getSelected(InputFilterDto query) {
String selectedJSON = query.get("selected").orElseThrow(IllegalArgumentException::new);
List<String> selected = new Gson().fromJson(selectedJSON, new TypeToken<List<String>>() {}.getType());
if (selected.isEmpty()) throw new CompleteSetException();

View File

@ -16,11 +16,11 @@
*/
package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.settings.locale.lang.FilterLang;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.filter.CompleteSetException;
import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation;
import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries;
import javax.inject.Inject;
@ -57,7 +57,7 @@ public class OperatorsFilter extends MultiOptionFilter {
}
@Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) {
public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
List<String> selected = getSelected(query);
Set<UUID> uuids = new HashSet<>();
String[] options = getOptionsArray();

View File

@ -16,14 +16,16 @@
*/
package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation;
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
import com.djrapitops.plan.storage.database.queries.objects.SessionQueries;
import com.google.gson.Gson;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Set;
import java.util.UUID;
import java.util.*;
@Singleton
public class PlayedBetweenDateRangeFilter extends DateRangeFilter {
@ -42,9 +44,18 @@ public class PlayedBetweenDateRangeFilter extends DateRangeFilter {
}
@Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) {
public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
long after = getAfter(query);
long before = getBefore(query);
return dbSystem.getDatabase().query(SessionQueries.uuidsOfPlayedBetween(after, before));
List<String> serverNames = getServerNames(query);
List<ServerUUID> serverUUIDs = serverNames.isEmpty() ? Collections.emptyList() : dbSystem.getDatabase().query(ServerQueries.fetchServersMatchingIdentifiers(serverNames));
return dbSystem.getDatabase().query(SessionQueries.uuidsOfPlayedBetween(after, before, serverUUIDs));
}
private List<String> getServerNames(InputFilterDto query) {
return query.get("servers")
.map(serversList -> new Gson().fromJson(serversList, String[].class))
.map(Arrays::asList)
.orElseGet(Collections::emptyList);
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.*;
@Singleton
public class PlayedOnServerFilter extends MultiOptionFilter {
private final DBSystem dbSystem;
@Inject
public PlayedOnServerFilter(DBSystem dbSystem) {
this.dbSystem = dbSystem;
}
@Override
public String getKind() {
return "playedOnServer";
}
@Override
public Map<String, Object> getOptions() {
return Collections.singletonMap("options", getSelectionOptions());
}
private List<String> getSelectionOptions() {
return dbSystem.getDatabase().query(ServerQueries.fetchGameServerNames());
}
@Override
public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
List<String> serverNames = getSelected(query);
List<ServerUUID> serverUUIDs = serverNames.isEmpty() ? Collections.emptyList() : dbSystem.getDatabase().query(ServerQueries.fetchServersMatchingIdentifiers(serverNames));
return dbSystem.getDatabase().query(UserInfoQueries.uuidsOfRegisteredBetween(0, System.currentTimeMillis(), serverUUIDs));
}
}

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.DBSystem;
@ -23,7 +24,6 @@ import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.queries.Query;
import com.djrapitops.plan.storage.database.queries.QueryAllStatement;
import com.djrapitops.plan.storage.database.queries.QueryStatement;
import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation;
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionPlayerValueTable;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionPluginTable;
@ -164,7 +164,7 @@ public class PluginBooleanGroupFilter extends MultiOptionFilter {
}
@Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) {
public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
Map<PluginBooleanOption, SelectedBoolean> selectedBooleanOptions = new HashMap<>();
for (String selected : getSelected(query)) {
String[] optionAndBoolean = StringUtils.split(selected, ":", 2);

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.extension.implementation.providers.ProviderIdentifier;
import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionUUIDsInGroupQuery;
import com.djrapitops.plan.identification.Server;
@ -23,7 +24,6 @@ import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.QueryAllStatement;
import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionGroupsTable;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionPluginTable;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderTable;
@ -67,7 +67,7 @@ public class PluginGroupsFilter extends MultiOptionFilter {
}
@Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) {
public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
return dbSystem.getDatabase().query(
new ExtensionUUIDsInGroupQuery(identifier.getPluginName(), identifier.getProviderName(), identifier.getServerUUID(), getSelected(query))
);

View File

@ -16,14 +16,17 @@
*/
package com.djrapitops.plan.storage.database.queries.filter.filters;
import com.djrapitops.plan.delivery.domain.datatransfer.InputFilterDto;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.filter.SpecifiedFilterInformation;
import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries;
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
import com.djrapitops.plan.storage.database.queries.objects.UserInfoQueries;
import com.google.gson.Gson;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Set;
import java.util.UUID;
import java.util.*;
@Singleton
public class RegisteredBetweenDateRangeFilter extends DateRangeFilter {
@ -42,9 +45,21 @@ public class RegisteredBetweenDateRangeFilter extends DateRangeFilter {
}
@Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) {
public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
long after = getAfter(query);
long before = getBefore(query);
return dbSystem.getDatabase().query(BaseUserQueries.uuidsOfRegisteredBetween(after, before));
List<String> serverNames = getServerNames(query);
List<ServerUUID> serverUUIDs = serverNames.isEmpty() ? Collections.emptyList() : dbSystem.getDatabase().query(ServerQueries.fetchServersMatchingIdentifiers(serverNames));
return dbSystem.getDatabase().query(
serverUUIDs.isEmpty() ? BaseUserQueries.uuidsOfRegisteredBetween(after, before)
: UserInfoQueries.uuidsOfRegisteredBetween(after, before, serverUUIDs)
);
}
private List<String> getServerNames(InputFilterDto query) {
return query.get("servers")
.map(serversList -> new Gson().fromJson(serversList, String[].class))
.map(Arrays::asList)
.orElseGet(Collections::emptyList);
}
}

View File

@ -30,6 +30,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
@ -172,6 +173,24 @@ public class ServerQueries {
};
}
public static Query<List<String>> fetchGameServerNames() {
String sql = Select.from(ServerTable.TABLE_NAME,
ServerTable.SERVER_ID, ServerTable.SERVER_UUID, ServerTable.NAME)
.where(ServerTable.PROXY + "=0")
.toString();
return new QueryAllStatement<List<String>>(sql) {
@Override
public List<String> processResults(ResultSet set) throws SQLException {
List<String> names = new ArrayList<>();
while (set.next()) {
names.add(Server.getIdentifiableName(set.getString(ServerTable.NAME), set.getInt(ServerTable.SERVER_ID)));
}
return names;
}
};
}
public static Query<Map<ServerUUID, String>> fetchServerNames() {
String sql = Select.from(ServerTable.TABLE_NAME,
ServerTable.SERVER_ID, ServerTable.SERVER_UUID, ServerTable.NAME)
@ -263,4 +282,14 @@ public class ServerQueries {
public static Query<Map<String, ServerUUID>> fetchServerNamesToUUIDs() {
return db -> Maps.reverse(db.query(fetchServerNames()));
}
public static Query<List<ServerUUID>> fetchServersMatchingIdentifiers(List<String> serverNames) {
return db -> {
Map<String, ServerUUID> nameToUUIDMap = db.query(ServerQueries.fetchServerNamesToUUIDs());
return serverNames.stream()
.map(nameToUUIDMap::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
};
}
}

View File

@ -846,11 +846,12 @@ public class SessionQueries {
};
}
public static Query<Set<UUID>> uuidsOfPlayedBetween(long after, long before) {
public static Query<Set<UUID>> uuidsOfPlayedBetween(long after, long before, List<ServerUUID> serverUUIDs) {
String sql = SELECT + DISTINCT + SessionsTable.USER_UUID +
FROM + SessionsTable.TABLE_NAME +
WHERE + SessionsTable.SESSION_END + ">=?" +
AND + SessionsTable.SESSION_START + "<=?";
AND + SessionsTable.SESSION_START + "<=?" +
(serverUUIDs.isEmpty() ? "" : AND + SessionsTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')");
return new QueryStatement<Set<UUID>>(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
@ -869,7 +870,7 @@ public class SessionQueries {
};
}
public static Query<Map<String, Long>> summaryOfPlayers(Set<UUID> playerUUIDs, long after, long before) {
public static Query<Map<String, Long>> summaryOfPlayers(Set<UUID> playerUUIDs, List<ServerUUID> serverUUIDs, long after, long before) {
String selectAggregates = SELECT +
"SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime," +
"SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME + ") as active_playtime," +
@ -878,7 +879,8 @@ public class SessionQueries {
WHERE + SessionsTable.SESSION_START + ">?" +
AND + SessionsTable.SESSION_END + "<?" +
AND + SessionsTable.USER_UUID + " IN ('" +
new TextStringBuilder().appendWithSeparators(playerUUIDs, "','").build() + "')";
new TextStringBuilder().appendWithSeparators(playerUUIDs, "','").build() + "')" +
(serverUUIDs.isEmpty() ? "" : AND + SessionsTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')");
return new QueryStatement<Map<String, Long>>(selectAggregates) {
@Override

View File

@ -22,6 +22,7 @@ import com.djrapitops.plan.storage.database.queries.Query;
import com.djrapitops.plan.storage.database.queries.QueryAllStatement;
import com.djrapitops.plan.storage.database.queries.QueryStatement;
import com.djrapitops.plan.storage.database.sql.tables.UserInfoTable;
import com.djrapitops.plan.storage.database.sql.tables.UsersTable;
import com.djrapitops.plan.utilities.java.Lists;
import org.apache.commons.text.TextStringBuilder;
@ -338,4 +339,28 @@ public class UserInfoQueries {
}
};
}
public static Query<Set<UUID>> uuidsOfRegisteredBetween(long after, long before, List<ServerUUID> serverUUIDs) {
String sql = SELECT + DISTINCT + UserInfoTable.USER_UUID +
FROM + UserInfoTable.TABLE_NAME +
WHERE + UserInfoTable.REGISTERED + ">=?" +
AND + UserInfoTable.REGISTERED + "<=?" +
AND + UserInfoTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')";
return new QueryStatement<Set<UUID>>(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setLong(1, after);
statement.setLong(2, before);
}
@Override
public Set<UUID> processResults(ResultSet set) throws SQLException {
Set<UUID> uuids = new HashSet<>();
while (set.next()) {
uuids.add(UUID.fromString(set.getString(UsersTable.USER_UUID)));
}
return uuids;
}
};
}
}

View File

@ -18,6 +18,7 @@ package com.djrapitops.plan.storage.database.queries.objects.playertable;
import com.djrapitops.plan.delivery.domain.TablePlayer;
import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.SQLDB;
import com.djrapitops.plan.storage.database.queries.Query;
import com.djrapitops.plan.storage.database.queries.QueryStatement;
@ -46,6 +47,7 @@ import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
public class QueryTablePlayersQuery implements Query<List<TablePlayer>> {
private final Collection<UUID> playerUUIDs;
private final List<ServerUUID> serverUUIDs;
private final long afterDate;
private final long beforeDate;
private final long activeMsThreshold;
@ -54,12 +56,14 @@ public class QueryTablePlayersQuery implements Query<List<TablePlayer>> {
* Create a new query.
*
* @param playerUUIDs UUIDs of the players in the query
* @param beforeDate View data before this epoch ms
* @param serverUUIDs View data for these Server UUIDs
* @param afterDate View data after this epoch ms
* @param beforeDate View data before this epoch ms
* @param activeMsThreshold Playtime threshold for Activity Index calculation
*/
public QueryTablePlayersQuery(Collection<UUID> playerUUIDs, long afterDate, long beforeDate, long activeMsThreshold) {
public QueryTablePlayersQuery(Collection<UUID> playerUUIDs, List<ServerUUID> serverUUIDs, long afterDate, long beforeDate, long activeMsThreshold) {
this.playerUUIDs = playerUUIDs;
this.serverUUIDs = serverUUIDs;
this.afterDate = afterDate;
this.beforeDate = beforeDate;
this.activeMsThreshold = activeMsThreshold;
@ -95,12 +99,14 @@ public class QueryTablePlayersQuery implements Query<List<TablePlayer>> {
AND + "s." + SessionsTable.SESSION_END + "<=?" +
AND + "s." + SessionsTable.USER_UUID +
uuidsInSet +
(serverUUIDs.isEmpty() ? "" : AND + "s." + SessionsTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')") +
GROUP_BY + "s." + SessionsTable.USER_UUID;
String selectBanned = SELECT + DISTINCT + "ub." + UserInfoTable.USER_UUID +
FROM + UserInfoTable.TABLE_NAME + " ub" +
WHERE + UserInfoTable.BANNED + "=?" +
AND + UserInfoTable.USER_UUID + uuidsInSet;
AND + UserInfoTable.USER_UUID + uuidsInSet +
(serverUUIDs.isEmpty() ? "" : AND + UserInfoTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')");
String selectBaseUsers = SELECT +
"u." + UsersTable.USER_UUID + ',' +

View File

@ -869,6 +869,30 @@ div#navSrvContainer::-webkit-scrollbar-thumb {
color: #fff;
}
.btn.bg-plan:hover,
.btn.bg-pink:hover,
.btn.bg-red:hover,
.btn.bg-purple:hover,
.btn.bg-deep-purple:hover,
.btn.bg-indigo:hover,
.btn.bg-light-blue:hover,
.btn.bg-black:hover,
.btn.bg-blue:hover,
.btn.bg-cyan:hover,
.btn.bg-teal:hover,
.btn.bg-green:hover,
.btn.bg-light-green:hover,
.btn.bg-lime:hover,
.btn.bg-yellow:hover,
.btn.bg-amber:hover,
.btn.bg-orange:hover,
.btn.bg-deep-orange:hover,
.btn.bg-brown:hover,
.btn.bg-grey:hover,
.btn.bg-blue-grey:hover {
color: #ccc;
}
.bg-night, body.theme-night .fc-toolbar-chunk .btn.btn-primary {
background-color: #44475a;
color: #eee8d5;

View File

@ -209,6 +209,12 @@ class RegisteredBetweenFilter extends BetweenDateFilter {
}
}
class PlayedOnServerFilter extends MultipleChoiceFilter {
constructor(id, options) {
super(id, "playedOnServer", "have played on at least one of", options);
}
}
function createFilter(filter, id) {
if (filter.kind.startsWith("pluginGroups-")) {
return new PluginGroupsFilter(id, filter.kind, filter.options);
@ -230,6 +236,8 @@ function createFilter(filter, id) {
return new RegisteredBetweenFilter(id, filter.options);
case "pluginsBooleanGroups":
return new PluginBooleanGroupsFilter(id, filter.options);
case "playedOnServer":
return new PlayedOnServerFilter(id, filter.options);
default:
throw new Error("Unsupported filter kind: '" + filter.kind + "'");
}
@ -258,6 +266,8 @@ function getReadableFilterName(filter) {
return "Registered between";
case "pluginsBooleanGroups":
return "Has plugin boolean value";
case "playedOnServer":
return "Has played on one of servers";
default:
return filter.kind;
}

View File

@ -6,7 +6,8 @@ const queryState = {
afterDate: null,
afterTime: null,
beforeDate: null,
beforeTime: null
beforeTime: null,
servers: []
},
invalidFormFields: {
ids: [],
@ -34,6 +35,8 @@ const queryState = {
let timestamp = undefined;
let serverMap = {};
function loadView(json) {
queryState.view = json.view;
@ -42,11 +45,36 @@ function loadView(json) {
document.getElementById('viewToDateField').setAttribute('placeholder', json.view.beforeDate);
document.getElementById('viewToTimeField').setAttribute('placeholder', json.view.beforeTime);
// Load server selector or hide it
if (json.view.servers.length >= 2) {
let options = ``;
for (let server of json.view.servers) {
if (server.proxy) continue;
serverMap[server.serverUUID] = server;
options += `<option data-plan-server-uuid="${server.serverUUID}">${server.serverName}</option>`
}
const serverSelector = document.getElementById("server-selector");
serverSelector.innerHTML = options;
serverSelector.addEventListener('click', () => {
queryState.view.servers = [];
if (serverSelector.selectedOptions.length !== serverSelector.options.length) {
for (const option of serverSelector.selectedOptions) {
queryState.view.servers.push(serverMap[option.getAttribute('data-plan-server-uuid')]);
}
}
document.getElementById("serverDropdown").innerText = queryState.view.servers.length
? `using data of ${queryState.view.servers.length} server(s)`
: "using data of all servers"
})
} else {
document.getElementById("serverDropdown").classList.add("hidden");
}
const playersOnlineSeries = {
name: 'Players Online', type: 'areaspline', tooltip: {valueDecimals: 0},
data: json.viewPoints, color: '#9E9E9E', yAxis: 0
}
graphs.push(Highcharts.stockChart('viewChart', {
rangeSelector: {
selected: 3,
@ -424,7 +452,8 @@ function displayDataResultScreen(resultCount, view) {
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 fw-bold col-black" title=" ${afterDate} ${afterTime} - ${beforeDate} ${beforeTime}"><i
class="fas fa-fw fa-users col-black"></i>
View: ${afterDate} - ${beforeDate}</h6>
View: ${afterDate} - ${beforeDate},
${view.servers.length ? "using data of servers: " + view.servers.map(server => server.serverName).join(', ') : "using data of all servers"}</h6>
</div>
<table class="table table-bordered table-striped table-hover player-table" style="width: 100%">
<tr>

View File

@ -141,6 +141,21 @@
</div>
<div class="chart-area" id="viewChart"><span class="loader"></span></div>
<div>
<button aria-expanded="false" aria-haspopup="true" class="btn dropdown-toggle"
data-bs-target="#server-dropdown" data-bs-toggle="collapse"
id="serverDropdown" type="button">using data of all servers
</button>
<div aria-labelledby="serverDropdown" class="collapse"
id="server-dropdown">
<select class="form-control" id="server-selector" multiple>
<option selected>Proxy server</option>
<option>Server 1</option>
<option>Skyblock</option>
<option>Server 3</option>
</select>
</div>
</div>
<hr>
<div id="filters"></div>

View File

@ -22,6 +22,7 @@ import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.settings.locale.LocaleSystem;
import com.djrapitops.plan.storage.file.JarResource;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import com.google.gson.Gson;
import dagger.Module;
import dagger.Provides;
import utilities.TestErrorLogger;
@ -37,6 +38,12 @@ import java.util.function.Predicate;
@Module
public class TestSystemObjectProvidingModule {
@Provides
@Singleton
Gson provideGson() {
return new Gson();
}
@Provides
@Singleton
Locale provideLocale(LocaleSystem localeSystem) {