Skip to content

Commit

Permalink
2360/plugin versions (#3249)
Browse files Browse the repository at this point in the history
* Add methods to gather plugin versions from servers
* Gathering and storage for plugin version history
* Test plugin gathering
* Test plugin metadata storage
* /v1/pluginHistory endpoint
* Plugin history tab
* Plugin history to performance tab
* Possibly fix ConfigChange.MovedValue being applied all the time
* Updated locale files
* Export pluginHistory for server page
* Add plugin history to network page
* Access control and improvements
* Remove pluginHistory from export since it now requires auth
* Fix access visibility tests
* Fix VelocitySensor during test

Affects issues:
- Close #2360
  • Loading branch information
AuroraLS3 committed Oct 7, 2023
1 parent 61db136 commit 5a2bdaf
Show file tree
Hide file tree
Showing 66 changed files with 1,464 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@
*/
package com.djrapitops.plan.gathering;

import com.djrapitops.plan.gathering.domain.PluginMetadata;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -129,4 +132,12 @@ public List<String> getOnlinePlayerNames() {
.map(Player::getName)
.collect(Collectors.toList());
}

@Override
public List<PluginMetadata> getInstalledPlugins() {
return Arrays.stream(Bukkit.getPluginManager().getPlugins())
.map(Plugin::getDescription)
.map(description -> new PluginMetadata(description.getName(), description.getVersion()))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.djrapitops.plan.gathering.ShutdownDataPreservation;
import com.djrapitops.plan.gathering.ShutdownHook;
import com.djrapitops.plan.gathering.timed.BukkitPingCounter;
import com.djrapitops.plan.gathering.timed.InstalledPluginGatheringTask;
import com.djrapitops.plan.gathering.timed.ServerTPSCounter;
import com.djrapitops.plan.gathering.timed.SystemUsageBuffer;
import com.djrapitops.plan.settings.upkeep.ConfigStoreTask;
Expand Down Expand Up @@ -108,4 +109,8 @@ public interface BukkitTaskModule {
@Binds
@IntoSet
TaskSystem.Task bindAddressAllowListUpdateTask(AddressAllowList addressAllowList);

@Binds
@IntoSet
TaskSystem.Task bindInstalledPluginGatheringTask(InstalledPluginGatheringTask installedPluginGatheringTask);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package com.djrapitops.plan.gathering;

import com.djrapitops.plan.PlanBungee;
import com.djrapitops.plan.gathering.domain.PluginMetadata;
import com.djrapitops.plan.identification.properties.RedisCheck;
import com.djrapitops.plan.identification.properties.RedisPlayersOnlineSupplier;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;

import javax.inject.Inject;
import javax.inject.Singleton;
Expand All @@ -35,11 +37,13 @@ public class BungeeSensor implements ServerSensor<Object> {
private final IntSupplier onlinePlayerCountSupplier;
private final IntSupplier onlinePlayerCountBungee;
private final Supplier<Collection<ProxiedPlayer>> getPlayers;
private final Supplier<Collection<Plugin>> getPlugins;

@Inject
public BungeeSensor(PlanBungee plugin) {
getPlayers = plugin.getProxy()::getPlayers;
onlinePlayerCountBungee = plugin.getProxy()::getOnlineCount;
getPlugins = plugin.getProxy().getPluginManager()::getPlugins;
onlinePlayerCountSupplier = RedisCheck.isClassAvailable() ? new RedisPlayersOnlineSupplier() : onlinePlayerCountBungee;
}

Expand All @@ -63,4 +67,12 @@ public List<String> getOnlinePlayerNames() {
public boolean usingRedisBungee() {
return RedisCheck.isClassAvailable();
}

@Override
public List<PluginMetadata> getInstalledPlugins() {
return getPlugins.get().stream()
.map(Plugin::getDescription)
.map(description -> new PluginMetadata(description.getName(), description.getVersion()))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.djrapitops.plan.delivery.webserver.configuration.AddressAllowList;
import com.djrapitops.plan.extension.ExtensionServerDataUpdater;
import com.djrapitops.plan.gathering.timed.BungeePingCounter;
import com.djrapitops.plan.gathering.timed.InstalledPluginGatheringTask;
import com.djrapitops.plan.gathering.timed.ProxyTPSCounter;
import com.djrapitops.plan.gathering.timed.SystemUsageBuffer;
import com.djrapitops.plan.settings.upkeep.NetworkConfigStoreTask;
Expand Down Expand Up @@ -87,4 +88,8 @@ public interface BungeeTaskModule {
@Binds
@IntoSet
TaskSystem.Task bindAddressAllowListUpdateTask(AddressAllowList addressAllowList);

@Binds
@IntoSet
TaskSystem.Task bindInstalledPluginGatheringTask(InstalledPluginGatheringTask installedPluginGatheringTask);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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;

import org.jetbrains.annotations.Nullable;

import java.util.Objects;

/**
* Represents plugin version history.
* <p>
* If version is null the plugin was uninstalled at that time.
*
* @author AuroraLS3
*/
public class PluginHistoryMetadata {

private final String name;
@Nullable
private final String version;
private final long modified;

public PluginHistoryMetadata(String name, @Nullable String version, long modified) {
this.name = name;
this.version = version;
this.modified = modified;
}

public String getName() {
return name;
}

@Nullable
public String getVersion() {
return version;
}

public long getModified() {
return modified;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PluginHistoryMetadata that = (PluginHistoryMetadata) o;
return getModified() == that.getModified() && Objects.equals(getName(), that.getName()) && Objects.equals(getVersion(), that.getVersion());
}

@Override
public int hashCode() {
return Objects.hash(getName(), getVersion(), getModified());
}

@Override
public String toString() {
return "PluginHistoryMetadata{" +
"name='" + name + '\'' +
", version='" + version + '\'' +
", modified=" + modified +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public enum WebPermission implements Supplier<String>, Lang {
PAGE_NETWORK_GEOLOCATIONS_PING_PER_COUNTRY("See Ping Per Country table"),
PAGE_NETWORK_PLAYERS("See Player list -tab"),
PAGE_NETWORK_PERFORMANCE("See network Performance tab"),
PAGE_NETWORK_PLUGIN_HISTORY("See Plugin History across the network"),
PAGE_NETWORK_PLUGINS("See Plugins tab of Proxy"),

PAGE_SERVER("See all of server page"),
Expand Down Expand Up @@ -90,6 +91,7 @@ public enum WebPermission implements Supplier<String>, Lang {
PAGE_SERVER_PERFORMANCE("See Performance tab"),
PAGE_SERVER_PERFORMANCE_GRAPHS("See Performance graphs"),
PAGE_SERVER_PERFORMANCE_OVERVIEW("See Performance numbers"),
PAGE_SERVER_PLUGIN_HISTORY("See Plugin History"),
PAGE_SERVER_PLUGINS("See Plugins -tabs of servers"),

PAGE_PLAYER("See all of player page"),
Expand Down
Original file line number Diff line number Diff line change
@@ -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.delivery.domain.PluginHistoryMetadata;

import java.util.List;
import java.util.Objects;

/**
* History of plugin versions, sorted most recent first.
*
* @author AuroraLS3
*/
public class PluginHistoryDto {

private final List<PluginHistoryMetadata> history;

public PluginHistoryDto(List<PluginHistoryMetadata> history) {
this.history = history;
}

public List<PluginHistoryMetadata> getHistory() {
return history;
}

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

@Override
public int hashCode() {
return Objects.hash(getHistory());
}

@Override
public String toString() {
return "PluginHistoryDto{" +
"history=" + history +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.djrapitops.plan.delivery.webserver.http.WebServer;
import com.djrapitops.plan.delivery.webserver.resolver.json.metadata.PreferencesJSONResolver;
import com.djrapitops.plan.delivery.webserver.resolver.json.metadata.StorePreferencesJSONResolver;
import com.djrapitops.plan.delivery.webserver.resolver.json.plugins.PluginHistoryJSONResolver;
import com.djrapitops.plan.identification.Identifiers;
import dagger.Lazy;

Expand All @@ -49,6 +50,7 @@ public class RootJSONResolver {

private final CompositeResolver.Builder readOnlyResourcesBuilder;
private final StorePreferencesJSONResolver storePreferencesJSONResolver;
private final PluginHistoryJSONResolver pluginHistoryJSONResolver;
private CompositeResolver resolver;

@Inject
Expand Down Expand Up @@ -84,6 +86,7 @@ public RootJSONResolver(
ExtensionJSONResolver extensionJSONResolver,
RetentionJSONResolver retentionJSONResolver,
PlayerJoinAddressJSONResolver playerJoinAddressJSONResolver,
PluginHistoryJSONResolver pluginHistoryJSONResolver,

PreferencesJSONResolver preferencesJSONResolver,
StorePreferencesJSONResolver storePreferencesJSONResolver,
Expand Down Expand Up @@ -127,6 +130,7 @@ public RootJSONResolver(

this.webServer = webServer;
// These endpoints require authentication to be enabled.
this.pluginHistoryJSONResolver = pluginHistoryJSONResolver;
this.webGroupJSONResolver = webGroupJSONResolver;
this.webGroupPermissionJSONResolver = webGroupPermissionJSONResolver;
this.webPermissionJSONResolver = webPermissionJSONResolver;
Expand All @@ -149,6 +153,7 @@ public CompositeResolver getResolver() {
.add("saveGroupPermissions", webGroupSaveJSONResolver)
.add("deleteGroup", webGroupDeleteJSONResolver)
.add("storePreferences", storePreferencesJSONResolver)
.add("pluginHistory", pluginHistoryJSONResolver)
.build();
} else {
resolver = readOnlyResourcesBuilder.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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.webserver.resolver.json.plugins;

import com.djrapitops.plan.delivery.domain.PluginHistoryMetadata;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.domain.datatransfer.PluginHistoryDto;
import com.djrapitops.plan.delivery.web.resolver.MimeType;
import com.djrapitops.plan.delivery.web.resolver.Resolver;
import com.djrapitops.plan.delivery.web.resolver.Response;
import com.djrapitops.plan.delivery.web.resolver.request.Request;
import com.djrapitops.plan.identification.Identifiers;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.queries.objects.PluginMetadataQueries;
import com.djrapitops.plan.utilities.dev.Untrusted;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
import java.util.Optional;

/**
* Endpoint for getting plugin version history.
*
* @author AuroraLS3
*/
@Singleton
@Path("/v1/pluginHistory")
public class PluginHistoryJSONResolver implements Resolver {

private final DBSystem dbSystem;
private final Identifiers identifiers;

@Inject
public PluginHistoryJSONResolver(DBSystem dbSystem, Identifiers identifiers) {
this.dbSystem = dbSystem;
this.identifiers = identifiers;
}

@Override
public boolean canAccess(Request request) {
return request.getUser()
.map(user -> user.hasPermission(WebPermission.PAGE_NETWORK_PLUGIN_HISTORY)
|| user.hasPermission(WebPermission.PAGE_SERVER_PLUGIN_HISTORY))
.orElse(false);
}

@Override
@Operation(
description = "Get plugin history for a server since installation of Plan.",
responses = {
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON,
schema = @Schema(implementation = PluginHistoryDto.class))),
},
parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = {
@ExampleObject("Server 1"),
@ExampleObject("1"),
@ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"),
}),
requestBody = @RequestBody(content = @Content(examples = @ExampleObject()))
)
@GET
public Optional<Response> resolve(@Untrusted Request request) {
return Optional.of(getResponse(request));
}

private Response getResponse(@Untrusted Request request) {
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
List<PluginHistoryMetadata> history = dbSystem.getDatabase().query(PluginMetadataQueries.getPluginHistory(serverUUID));
return Response.builder()
.setMimeType(MimeType.JSON)
.setJSONContent(new PluginHistoryDto(history))
.build();
}
}
Loading

0 comments on commit 5a2bdaf

Please sign in to comment.