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 * You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>. * 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.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
@ -27,18 +28,18 @@ import java.util.*;
* *
* @author AuroraLS3 * @author AuroraLS3
*/ */
public class SpecifiedFilterInformation { public class InputFilterDto {
private final String kind; private final String kind;
private final Map<String, String> parameters; 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.kind = kind;
this.parameters = parameters; this.parameters = parameters;
} }
public static List<SpecifiedFilterInformation> parse(String json) throws IOException { public static List<InputFilterDto> parse(String json, Gson gson) throws IOException {
return new Gson().getAdapter(new TypeToken<List<SpecifiedFilterInformation>>() {}).fromJson(json); return gson.getAdapter(new TypeToken<List<InputFilterDto>>() {}).fromJson(json);
} }
public String getKind() { 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; package com.djrapitops.plan.delivery.rendering.json;
import com.djrapitops.plan.delivery.domain.DateObj; 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.PlayerKillMutator;
import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator; import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator;
import com.djrapitops.plan.delivery.domain.mutators.TPSMutator; import com.djrapitops.plan.delivery.domain.mutators.TPSMutator;
@ -266,16 +267,10 @@ public class JSONFactory {
return tableEntries; return tableEntries;
} }
public Map<String, Object> listServers() { public Map<String, List<ServerDto>> listServers() {
Collection<Server> servers = dbSystem.getDatabase().query(ServerQueries.fetchPlanServerInformationCollection()); Collection<Server> servers = dbSystem.getDatabase().query(ServerQueries.fetchPlanServerInformationCollection());
return Maps.builder(String.class, Object.class) return Collections.singletonMap("servers", servers.stream()
.put("servers", servers.stream() .map(ServerDto::fromServer)
.map(server -> Maps.builder(String.class, Object.class) .collect(Collectors.toList()));
.put("serverUUID", server.getUuid().toString())
.put("serverName", server.getIdentifiableName())
.put("proxy", server.isProxy())
.build())
.collect(Collectors.toList()))
.build();
} }
} }

View File

@ -89,6 +89,7 @@ public class WebAssetVersionCheckTask extends TaskSystem.Task {
if (!outdated.isEmpty()) { if (!outdated.isEmpty()) {
logger.warn("You have customized files which are out of date due to recent updates!"); 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("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) { for (AssetInfo asset : outdated) {
logger.warn(String.format("- %s was modified %s, but the plugin contains a version from %s", 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.Request;
import com.djrapitops.plan.delivery.web.resolver.request.URIQuery; 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 { public class RequestBodyConverter {
@ -35,9 +39,17 @@ public class RequestBodyConverter {
"POST".equalsIgnoreCase(request.getMethod()) && "POST".equalsIgnoreCase(request.getMethod()) &&
"application/x-www-form-urlencoded".equalsIgnoreCase(request.getHeader("Content-type").orElse("")) "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 { } else {
return new URIQuery(""); 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; package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.DateObj; 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.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.Graphs;
import com.djrapitops.plan.delivery.rendering.json.graphs.line.LineGraph; import com.djrapitops.plan.delivery.rendering.json.graphs.line.LineGraph;
import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point; 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.java.Lists;
import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger; import com.djrapitops.plan.utilities.logging.ErrorLogger;
import org.apache.commons.lang3.StringUtils;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -50,6 +51,7 @@ public class FiltersJSONResolver implements Resolver {
private final ServerInfo serverInfo; private final ServerInfo serverInfo;
private final DBSystem dbSystem; private final DBSystem dbSystem;
private final QueryFilters filters; private final QueryFilters filters;
private final JSONFactory jsonFactory;
private final Graphs graphs; private final Graphs graphs;
private final Formatters formatters; private final Formatters formatters;
private final ErrorLogger errorLogger; private final ErrorLogger errorLogger;
@ -59,6 +61,7 @@ public class FiltersJSONResolver implements Resolver {
ServerInfo serverInfo, ServerInfo serverInfo,
DBSystem dbSystem, DBSystem dbSystem,
QueryFilters filters, QueryFilters filters,
JSONFactory jsonFactory,
Graphs graphs, Graphs graphs,
Formatters formatters, Formatters formatters,
ErrorLogger errorLogger ErrorLogger errorLogger
@ -66,6 +69,7 @@ public class FiltersJSONResolver implements Resolver {
this.serverInfo = serverInfo; this.serverInfo = serverInfo;
this.dbSystem = dbSystem; this.dbSystem = dbSystem;
this.filters = filters; this.filters = filters;
this.jsonFactory = jsonFactory;
this.graphs = graphs; this.graphs = graphs;
this.formatters = formatters; this.formatters = formatters;
this.errorLogger = errorLogger; this.errorLogger = errorLogger;
@ -85,9 +89,9 @@ public class FiltersJSONResolver implements Resolver {
private Response getResponse() { private Response getResponse() {
return Response.builder() return Response.builder()
.setMimeType(MimeType.JSON) .setMimeType(MimeType.JSON)
.setJSONContent(new FilterResponseJSON( .setJSONContent(new FilterResponseDto(
filters.getFilters(), filters.getFilters(),
new ViewJSON(formatters), new ViewDto(formatters, jsonFactory.listServers().get("servers")),
fetchViewGraphPoints() fetchViewGraphPoints()
)).build(); )).build();
} }
@ -111,17 +115,17 @@ public class FiltersJSONResolver implements Resolver {
/** /**
* JSON serialization class. * JSON serialization class.
*/ */
class FilterResponseJSON { class FilterResponseDto {
final List<FilterJSON> filters; final List<FilterDto> filters;
final ViewJSON view; final ViewDto view;
final List<Double[]> viewPoints; 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.viewPoints = viewPoints;
this.filters = new ArrayList<>(); this.filters = new ArrayList<>();
for (Map.Entry<String, Filter> entry : filtersByKind.entrySet()) { for (Map.Entry<String, Filter> entry : filtersByKind.entrySet()) {
try { try {
filters.add(new FilterJSON(entry.getKey(), entry.getValue())); filters.add(new FilterDto(entry.getKey(), entry.getValue()));
} catch (Exception e) { } catch (Exception e) {
errorLogger.error(e, ErrorContext.builder() errorLogger.error(e, ErrorContext.builder()
.whatToDo("Report this, filter '" + entry.getKey() + "' has implementation error.") .whatToDo("Report this, filter '" + entry.getKey() + "' has implementation error.")
@ -133,63 +137,4 @@ public class FiltersJSONResolver implements Resolver {
this.view = view; 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<Long> timeAmount;
private final Formatter<Double> percentage; private final Formatter<Double> percentage;
private final Formatter<Double> byteSize; private final Formatter<Double> byteSize;
private final Gson gson;
@Inject @Inject
public NetworkPerformanceJSONResolver( public NetworkPerformanceJSONResolver(
PlanConfig config, PlanConfig config,
Locale locale, Locale locale,
DBSystem dbSystem, DBSystem dbSystem,
Formatters formatters Formatters formatters,
Gson gson
) { ) {
this.config = config; this.config = config;
this.locale = locale; this.locale = locale;
@ -73,6 +75,7 @@ public class NetworkPerformanceJSONResolver implements Resolver {
percentage = formatters.percentage(); percentage = formatters.percentage();
timeAmount = formatters.timeAmount(); timeAmount = formatters.timeAmount();
byteSize = formatters.byteSize(); byteSize = formatters.byteSize();
this.gson = gson;
} }
@Override @Override
@ -93,7 +96,7 @@ public class NetworkPerformanceJSONResolver implements Resolver {
} }
private List<UUID> getUUIDList(String jsonString) { 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) { public Map<String, Object> createJSONAsMap(Collection<ServerUUID> serverUUIDs) {

View File

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

View File

@ -57,4 +57,8 @@ public interface FiltersModule {
@IntoSet @IntoSet
Filter filter8(PluginBooleanGroupFilter filter); 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.storage.file.JarResource;
import com.djrapitops.plan.utilities.logging.ErrorLogger; import com.djrapitops.plan.utilities.logging.ErrorLogger;
import com.djrapitops.plan.utilities.logging.PluginErrorLogger; import com.djrapitops.plan.utilities.logging.PluginErrorLogger;
import com.google.gson.Gson;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import dagger.multibindings.ElementsIntoSet; import dagger.multibindings.ElementsIntoSet;
@ -49,6 +50,12 @@ import java.util.function.Predicate;
@Module @Module
public class SystemObjectProvidingModule { public class SystemObjectProvidingModule {
@Provides
@Singleton
Gson provideGson() {
return new Gson();
}
@Provides @Provides
@ElementsIntoSet @ElementsIntoSet
Set<Importer> emptyImporterSet() { Set<Importer> emptyImporterSet() {

View File

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

View File

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

View File

@ -16,6 +16,7 @@
*/ */
package com.djrapitops.plan.storage.database.queries.filter; 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.delivery.web.resolver.exception.BadRequestException;
import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.filter.filters.AllPlayersFilter; 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. * @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. * @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(); prepareFilters();
Filter.Result current = null; Filter.Result current = null;
if (filterQueries.isEmpty()) return allPlayersFilter.apply(null); if (filterQueries.isEmpty()) return allPlayersFilter.apply(null);
for (SpecifiedFilterInformation specifiedFilterInformation : filterQueries) { for (InputFilterDto inputFilterDto : filterQueries) {
current = apply(current, specifiedFilterInformation); current = apply(current, inputFilterDto);
if (current != null && current.isEmpty()) break; if (current != null && current.isEmpty()) break;
} }
return current; return current;
} }
private Filter.Result apply(Filter.Result current, SpecifiedFilterInformation specifiedFilterInformation) { private Filter.Result apply(Filter.Result current, InputFilterDto inputFilterDto) {
String kind = specifiedFilterInformation.getKind(); String kind = inputFilterDto.getKind();
Filter filter = getFilter(kind).orElseThrow(() -> new BadRequestException("Filter kind not supported: '" + kind + "'")); 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 { try {
return current == null ? filter.apply(query) : current.apply(filter, query); return current == null ? filter.apply(query) : current.apply(filter, query);
} catch (IllegalArgumentException badOptions) { } catch (IllegalArgumentException badOptions) {

View File

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

View File

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

View File

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

View File

@ -16,10 +16,10 @@
*/ */
package com.djrapitops.plan.storage.database.queries.filter.filters; 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.delivery.web.resolver.exception.BadRequestException;
import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.filter.Filter; 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.storage.database.queries.objects.BaseUserQueries;
import com.djrapitops.plan.utilities.java.Maps; import com.djrapitops.plan.utilities.java.Maps;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -61,15 +61,15 @@ public abstract class DateRangeFilter implements Filter {
.build(); .build();
} }
protected long getAfter(SpecifiedFilterInformation query) { protected long getAfter(InputFilterDto query) {
return getTime(query, "afterDate", "afterTime"); return getTime(query, "afterDate", "afterTime");
} }
protected long getBefore(SpecifiedFilterInformation query) { protected long getBefore(InputFilterDto query) {
return getTime(query, "beforeDate", "beforeTime"); 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 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())); 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; 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.DBSystem;
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.GeoInfoQueries;
import javax.inject.Inject; import javax.inject.Inject;
@ -47,7 +47,7 @@ public class GeolocationsFilter extends MultiOptionFilter {
} }
@Override @Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) { public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
return dbSystem.getDatabase().query(GeoInfoQueries.uuidsOfPlayersWithGeolocations(getSelected(query))); return dbSystem.getDatabase().query(GeoInfoQueries.uuidsOfPlayersWithGeolocations(getSelected(query)));
} }
} }

View File

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

View File

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

View File

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

View File

@ -16,14 +16,16 @@
*/ */
package com.djrapitops.plan.storage.database.queries.filter.filters; 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.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.djrapitops.plan.storage.database.queries.objects.SessionQueries;
import com.google.gson.Gson;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.util.Set; import java.util.*;
import java.util.UUID;
@Singleton @Singleton
public class PlayedBetweenDateRangeFilter extends DateRangeFilter { public class PlayedBetweenDateRangeFilter extends DateRangeFilter {
@ -42,9 +44,18 @@ public class PlayedBetweenDateRangeFilter extends DateRangeFilter {
} }
@Override @Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) { public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
long after = getAfter(query); long after = getAfter(query);
long before = getBefore(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; 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.Server;
import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.DBSystem; 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.Query;
import com.djrapitops.plan.storage.database.queries.QueryAllStatement; import com.djrapitops.plan.storage.database.queries.QueryAllStatement;
import com.djrapitops.plan.storage.database.queries.QueryStatement; 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.queries.objects.ServerQueries;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionPlayerValueTable; import com.djrapitops.plan.storage.database.sql.tables.ExtensionPlayerValueTable;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionPluginTable; import com.djrapitops.plan.storage.database.sql.tables.ExtensionPluginTable;
@ -164,7 +164,7 @@ public class PluginBooleanGroupFilter extends MultiOptionFilter {
} }
@Override @Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) { public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
Map<PluginBooleanOption, SelectedBoolean> selectedBooleanOptions = new HashMap<>(); Map<PluginBooleanOption, SelectedBoolean> selectedBooleanOptions = new HashMap<>();
for (String selected : getSelected(query)) { for (String selected : getSelected(query)) {
String[] optionAndBoolean = StringUtils.split(selected, ":", 2); String[] optionAndBoolean = StringUtils.split(selected, ":", 2);

View File

@ -16,6 +16,7 @@
*/ */
package com.djrapitops.plan.storage.database.queries.filter.filters; 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.providers.ProviderIdentifier;
import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionUUIDsInGroupQuery; import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionUUIDsInGroupQuery;
import com.djrapitops.plan.identification.Server; 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.identification.ServerUUID;
import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.QueryAllStatement; 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.ExtensionGroupsTable;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionPluginTable; import com.djrapitops.plan.storage.database.sql.tables.ExtensionPluginTable;
import com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderTable; import com.djrapitops.plan.storage.database.sql.tables.ExtensionProviderTable;
@ -67,7 +67,7 @@ public class PluginGroupsFilter extends MultiOptionFilter {
} }
@Override @Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) { public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
return dbSystem.getDatabase().query( return dbSystem.getDatabase().query(
new ExtensionUUIDsInGroupQuery(identifier.getPluginName(), identifier.getProviderName(), identifier.getServerUUID(), getSelected(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; 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.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.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.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.util.Set; import java.util.*;
import java.util.UUID;
@Singleton @Singleton
public class RegisteredBetweenDateRangeFilter extends DateRangeFilter { public class RegisteredBetweenDateRangeFilter extends DateRangeFilter {
@ -42,9 +45,21 @@ public class RegisteredBetweenDateRangeFilter extends DateRangeFilter {
} }
@Override @Override
public Set<UUID> getMatchingUUIDs(SpecifiedFilterInformation query) { public Set<UUID> getMatchingUUIDs(InputFilterDto query) {
long after = getAfter(query); long after = getAfter(query);
long before = getBefore(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.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*; 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() { public static Query<Map<ServerUUID, String>> fetchServerNames() {
String sql = Select.from(ServerTable.TABLE_NAME, String sql = Select.from(ServerTable.TABLE_NAME,
ServerTable.SERVER_ID, ServerTable.SERVER_UUID, ServerTable.NAME) ServerTable.SERVER_ID, ServerTable.SERVER_UUID, ServerTable.NAME)
@ -263,4 +282,14 @@ public class ServerQueries {
public static Query<Map<String, ServerUUID>> fetchServerNamesToUUIDs() { public static Query<Map<String, ServerUUID>> fetchServerNamesToUUIDs() {
return db -> Maps.reverse(db.query(fetchServerNames())); 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 + String sql = SELECT + DISTINCT + SessionsTable.USER_UUID +
FROM + SessionsTable.TABLE_NAME + FROM + SessionsTable.TABLE_NAME +
WHERE + SessionsTable.SESSION_END + ">=?" + 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) { return new QueryStatement<Set<UUID>>(sql) {
@Override @Override
public void prepare(PreparedStatement statement) throws SQLException { 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 + String selectAggregates = SELECT +
"SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime," + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime," +
"SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME + ") as active_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 + ">?" + WHERE + SessionsTable.SESSION_START + ">?" +
AND + SessionsTable.SESSION_END + "<?" + AND + SessionsTable.SESSION_END + "<?" +
AND + SessionsTable.USER_UUID + " IN ('" + 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) { return new QueryStatement<Map<String, Long>>(selectAggregates) {
@Override @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.QueryAllStatement;
import com.djrapitops.plan.storage.database.queries.QueryStatement; 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.UserInfoTable;
import com.djrapitops.plan.storage.database.sql.tables.UsersTable;
import com.djrapitops.plan.utilities.java.Lists; import com.djrapitops.plan.utilities.java.Lists;
import org.apache.commons.text.TextStringBuilder; 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.TablePlayer;
import com.djrapitops.plan.delivery.domain.mutators.ActivityIndex; 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.SQLDB;
import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.Query;
import com.djrapitops.plan.storage.database.queries.QueryStatement; 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>> { public class QueryTablePlayersQuery implements Query<List<TablePlayer>> {
private final Collection<UUID> playerUUIDs; private final Collection<UUID> playerUUIDs;
private final List<ServerUUID> serverUUIDs;
private final long afterDate; private final long afterDate;
private final long beforeDate; private final long beforeDate;
private final long activeMsThreshold; private final long activeMsThreshold;
@ -54,12 +56,14 @@ public class QueryTablePlayersQuery implements Query<List<TablePlayer>> {
* Create a new query. * Create a new query.
* *
* @param playerUUIDs UUIDs of the players in the 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 afterDate View data after this epoch ms
* @param beforeDate View data before this epoch ms
* @param activeMsThreshold Playtime threshold for Activity Index calculation * @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.playerUUIDs = playerUUIDs;
this.serverUUIDs = serverUUIDs;
this.afterDate = afterDate; this.afterDate = afterDate;
this.beforeDate = beforeDate; this.beforeDate = beforeDate;
this.activeMsThreshold = activeMsThreshold; this.activeMsThreshold = activeMsThreshold;
@ -95,12 +99,14 @@ public class QueryTablePlayersQuery implements Query<List<TablePlayer>> {
AND + "s." + SessionsTable.SESSION_END + "<=?" + AND + "s." + SessionsTable.SESSION_END + "<=?" +
AND + "s." + SessionsTable.USER_UUID + AND + "s." + SessionsTable.USER_UUID +
uuidsInSet + uuidsInSet +
(serverUUIDs.isEmpty() ? "" : AND + "s." + SessionsTable.SERVER_UUID + " IN ('" + new TextStringBuilder().appendWithSeparators(serverUUIDs, "','") + "')") +
GROUP_BY + "s." + SessionsTable.USER_UUID; GROUP_BY + "s." + SessionsTable.USER_UUID;
String selectBanned = SELECT + DISTINCT + "ub." + UserInfoTable.USER_UUID + String selectBanned = SELECT + DISTINCT + "ub." + UserInfoTable.USER_UUID +
FROM + UserInfoTable.TABLE_NAME + " ub" + FROM + UserInfoTable.TABLE_NAME + " ub" +
WHERE + UserInfoTable.BANNED + "=?" + 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 + String selectBaseUsers = SELECT +
"u." + UsersTable.USER_UUID + ',' + "u." + UsersTable.USER_UUID + ',' +

View File

@ -869,6 +869,30 @@ div#navSrvContainer::-webkit-scrollbar-thumb {
color: #fff; 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 { .bg-night, body.theme-night .fc-toolbar-chunk .btn.btn-primary {
background-color: #44475a; background-color: #44475a;
color: #eee8d5; 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) { function createFilter(filter, id) {
if (filter.kind.startsWith("pluginGroups-")) { if (filter.kind.startsWith("pluginGroups-")) {
return new PluginGroupsFilter(id, filter.kind, filter.options); return new PluginGroupsFilter(id, filter.kind, filter.options);
@ -230,6 +236,8 @@ function createFilter(filter, id) {
return new RegisteredBetweenFilter(id, filter.options); return new RegisteredBetweenFilter(id, filter.options);
case "pluginsBooleanGroups": case "pluginsBooleanGroups":
return new PluginBooleanGroupsFilter(id, filter.options); return new PluginBooleanGroupsFilter(id, filter.options);
case "playedOnServer":
return new PlayedOnServerFilter(id, filter.options);
default: default:
throw new Error("Unsupported filter kind: '" + filter.kind + "'"); throw new Error("Unsupported filter kind: '" + filter.kind + "'");
} }
@ -258,6 +266,8 @@ function getReadableFilterName(filter) {
return "Registered between"; return "Registered between";
case "pluginsBooleanGroups": case "pluginsBooleanGroups":
return "Has plugin boolean value"; return "Has plugin boolean value";
case "playedOnServer":
return "Has played on one of servers";
default: default:
return filter.kind; return filter.kind;
} }

View File

@ -6,7 +6,8 @@ const queryState = {
afterDate: null, afterDate: null,
afterTime: null, afterTime: null,
beforeDate: null, beforeDate: null,
beforeTime: null beforeTime: null,
servers: []
}, },
invalidFormFields: { invalidFormFields: {
ids: [], ids: [],
@ -34,6 +35,8 @@ const queryState = {
let timestamp = undefined; let timestamp = undefined;
let serverMap = {};
function loadView(json) { function loadView(json) {
queryState.view = json.view; queryState.view = json.view;
@ -42,11 +45,36 @@ function loadView(json) {
document.getElementById('viewToDateField').setAttribute('placeholder', json.view.beforeDate); document.getElementById('viewToDateField').setAttribute('placeholder', json.view.beforeDate);
document.getElementById('viewToTimeField').setAttribute('placeholder', json.view.beforeTime); 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 = { const playersOnlineSeries = {
name: 'Players Online', type: 'areaspline', tooltip: {valueDecimals: 0}, name: 'Players Online', type: 'areaspline', tooltip: {valueDecimals: 0},
data: json.viewPoints, color: '#9E9E9E', yAxis: 0 data: json.viewPoints, color: '#9E9E9E', yAxis: 0
} }
graphs.push(Highcharts.stockChart('viewChart', { graphs.push(Highcharts.stockChart('viewChart', {
rangeSelector: { rangeSelector: {
selected: 3, 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"> <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 <h6 class="m-0 fw-bold col-black" title=" ${afterDate} ${afterTime} - ${beforeDate} ${beforeTime}"><i
class="fas fa-fw fa-users col-black"></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> </div>
<table class="table table-bordered table-striped table-hover player-table" style="width: 100%"> <table class="table table-bordered table-striped table-hover player-table" style="width: 100%">
<tr> <tr>

View File

@ -141,6 +141,21 @@
</div> </div>
<div class="chart-area" id="viewChart"><span class="loader"></span></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> <hr>
<div id="filters"></div> <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.settings.locale.LocaleSystem;
import com.djrapitops.plan.storage.file.JarResource; import com.djrapitops.plan.storage.file.JarResource;
import com.djrapitops.plan.utilities.logging.ErrorLogger; import com.djrapitops.plan.utilities.logging.ErrorLogger;
import com.google.gson.Gson;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import utilities.TestErrorLogger; import utilities.TestErrorLogger;
@ -37,6 +38,12 @@ import java.util.function.Predicate;
@Module @Module
public class TestSystemObjectProvidingModule { public class TestSystemObjectProvidingModule {
@Provides
@Singleton
Gson provideGson() {
return new Gson();
}
@Provides @Provides
@Singleton @Singleton
Locale provideLocale(LocaleSystem localeSystem) { Locale provideLocale(LocaleSystem localeSystem) {