Wrote an Exporter to export react bundle files

This commit is contained in:
Aurora Lahtela 2022-11-26 09:01:33 +02:00
parent 76f85b8d7d
commit 362105cf98
8 changed files with 211 additions and 7 deletions

View File

@ -120,6 +120,8 @@ task copyYarnBuildResults {
}
task determineAssetModifications {
dependsOn yarnBundle
inputs.files(fileTree("$rootDir/react/dashboard/build"))
inputs.files(fileTree(dir: 'src/main/resources/assets/plan/web'))
inputs.files(fileTree(dir: 'src/main/resources/assets/plan/locale'))
outputs.file("build/resources/main/assets/plan/AssetVersion.yml")
@ -139,7 +141,7 @@ task determineAssetModifications {
// git returns UNIX time in seconds, but most things in Java use UNIX time in milliseconds
def modified = gitModifiedAsString.isEmpty() ? System.currentTimeMillis() : Long.parseLong(gitModifiedAsString) * 1000
def relativePath = tree.getDir().toPath().relativize(f.toPath()) // File path relative to the tree
versionFile.text += String.format( // writing YAML as raw text probably isn't the best idea
versionFile.text += String.format(
"%s: %s\n", relativePath.toString().replace('.', ','), modified
)
}
@ -154,7 +156,16 @@ task determineAssetModifications {
// git returns UNIX time in seconds, but most things in Java use UNIX time in milliseconds
def modified = gitModifiedAsString.isEmpty() ? System.currentTimeMillis() : Long.parseLong(gitModifiedAsString) * 1000
def relativePath = tree.getDir().toPath().relativize(f.toPath()) // File path relative to the tree
versionFile.text += String.format( // writing YAML as raw text probably isn't the best idea
versionFile.text += String.format(
"%s: %s\n", relativePath.toString().replace('.', ','), modified
)
}
tree = fileTree("$rootDir/react/dashboard/build")
tree.forEach { File f ->
def modified = System.currentTimeMillis()
def relativePath = tree.getDir().toPath().relativize(f.toPath()) // File path relative to the tree
versionFile.text += String.format(
"%s: %s\n", relativePath.toString().replace('.', ','), modified
)
}

View File

@ -47,6 +47,7 @@ public class Exporter extends FileExporter {
private final NetworkPageExporter networkPageExporter;
private final Set<ServerUUID> failedServers;
private final ReactExporter reactExporter;
@Inject
public Exporter(
@ -55,7 +56,8 @@ public class Exporter extends FileExporter {
PlayerPageExporter playerPageExporter,
PlayersPageExporter playersPageExporter,
ServerPageExporter serverPageExporter,
NetworkPageExporter networkPageExporter
NetworkPageExporter networkPageExporter,
ReactExporter reactExporter
) {
this.config = config;
this.playerJSONExporter = playerJSONExporter;
@ -63,6 +65,7 @@ public class Exporter extends FileExporter {
this.playersPageExporter = playersPageExporter;
this.serverPageExporter = serverPageExporter;
this.networkPageExporter = networkPageExporter;
this.reactExporter = reactExporter;
failedServers = new HashSet<>();
}
@ -161,4 +164,14 @@ public class Exporter extends FileExporter {
throw new ExportException("Failed to export player: " + playerName + ", " + e.toString(), e);
}
}
public void exportReact() throws ExportException {
Path toDirectory = config.getPageExportPath();
try {
reactExporter.exportReactFiles(toDirectory);
} catch (IOException e) {
throw new ExportException("Failed to export react: " + e.toString(), e);
}
}
}

View File

@ -18,6 +18,7 @@ package com.djrapitops.plan.delivery.export;
import com.djrapitops.plan.delivery.rendering.html.Html;
import com.djrapitops.plan.delivery.web.resource.WebResource;
import com.djrapitops.plan.storage.file.Resource;
import org.apache.commons.lang3.StringUtils;
import java.io.ByteArrayInputStream;
@ -60,9 +61,15 @@ abstract class FileExporter {
export(to, Arrays.asList(StringUtils.split(content, "\r\n")));
}
void export(Path to, Resource resource) throws IOException {
export(to, resource.asWebResource());
}
void export(Path to, WebResource resource) throws IOException {
Path dir = to.getParent();
if (!Files.isSymbolicLink(dir)) Files.createDirectories(dir);
if (!Files.isSymbolicLink(dir) && !Files.isDirectory(dir)) {
Files.createDirectories(dir);
}
try (
InputStream in = resource.asStream();

View File

@ -0,0 +1,92 @@
/*
* 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.export;
import com.djrapitops.plan.delivery.web.AssetVersions;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
import com.djrapitops.plan.storage.file.PlanFiles;
import com.djrapitops.plan.storage.file.Resource;
import org.apache.commons.lang3.StringUtils;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
/**
* Exporter in charge of exporting React related files.
*
* @author AuroraLS3
*/
@Singleton
public class ReactExporter extends FileExporter {
private final PlanFiles files;
private final PlanConfig config;
private final AssetVersions assetVersions;
@Inject
public ReactExporter(
PlanFiles files,
PlanConfig config,
AssetVersions assetVersions
) {
this.files = files;
this.config = config;
this.assetVersions = assetVersions;
}
public void exportReactFiles(Path toDirectory) throws IOException {
exportAsset(toDirectory, "index.html");
exportAsset(toDirectory, "asset-manifest.json");
exportAsset(toDirectory, "favicon.ico");
exportAsset(toDirectory, "logo192.png");
exportAsset(toDirectory, "logo512.png");
exportAsset(toDirectory, "manifest.json");
exportAsset(toDirectory, "robots.txt");
exportStaticBundle(toDirectory);
}
private void exportStaticBundle(Path toDirectory) throws IOException {
List<String> paths = assetVersions.getAssetPaths().stream()
.filter(path -> path.contains("static"))
.collect(Collectors.toList());
for (String path : paths) {
String resourcePath = path.replace(',', '.');
Path to = toDirectory.resolve(resourcePath);
Resource resource = files.getResourceFromJar("web/" + resourcePath);
if (resourcePath.endsWith(".js")) {
String withReplacedConstants = StringUtils.replaceEach(
resource.asString(),
new String[]{"PLAN_BASE_ADDRESS", "PLAN_EXPORTED_VERSION"},
new String[]{config.get(WebserverSettings.EXTERNAL_LINK), "true"}
);
export(to, withReplacedConstants);
} else {
export(to, resource);
}
}
}
private void exportAsset(Path toDirectory, String asset) throws IOException {
export(toDirectory.resolve(asset), files.getResourceFromJar("web/" + asset));
}
}

View File

@ -24,6 +24,7 @@ import com.djrapitops.plan.storage.file.PlanFiles;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
@Singleton
@ -61,4 +62,9 @@ public class AssetVersions {
return Optional.of(max);
}
public List<String> getAssetPaths() throws IOException {
if (webAssetConfig == null) prepare();
return webAssetConfig.getConfigPaths();
}
}

View File

@ -0,0 +1,66 @@
/*
* 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.export;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.exceptions.ExportException;
import com.djrapitops.plan.settings.config.PlanConfig;
import extension.FullSystemExtension;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(FullSystemExtension.class)
class ReactExporterTest {
@BeforeAll
static void enableSystem(PlanSystem system) {
system.enable();
}
@AfterAll
static void disableSystem(PlanSystem system) {
system.disable();
}
@Test
void allReactFilesAreExported(PlanConfig config, Exporter exporter) throws ExportException, IOException {
Path exportPath = config.getPageExportPath();
exporter.exportReact();
Path reactBuildPath = Path.of(new File("").getAbsolutePath())
.resolve("../react/dashboard/build");
List<Path> filesToExport = Files.list(reactBuildPath)
.map(path -> path.relativize(reactBuildPath))
.collect(Collectors.toList());
List<Path> filesExported = Files.list(exportPath)
.map(path -> path.relativize(exportPath))
.collect(Collectors.toList());
assertEquals(filesToExport, filesExported);
}
}

View File

@ -17,6 +17,7 @@
package extension;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.delivery.export.Exporter;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
@ -80,7 +81,8 @@ public class FullSystemExtension implements ParameterResolver, BeforeAllCallback
PlanConfig.class.equals(type) ||
ServerUUID.class.equals(type) ||
PlanPluginComponent.class.equals(type) ||
Database.class.equals(type);
Database.class.equals(type) ||
Exporter.class.equals(type);
}
@Override
@ -105,6 +107,9 @@ public class FullSystemExtension implements ParameterResolver, BeforeAllCallback
if (Database.class.equals(type)) {
return planSystem.getDatabaseSystem().getDatabase();
}
if (Exporter.class.equals(type)) {
return planSystem.getExportSystem().getExporter();
}
throw new ParameterResolutionException("Unsupported parameter type " + type.getName());
}
}

View File

@ -1,6 +1,9 @@
import axios from "axios";
const toBeReplaced = "PLAN_BASE_ADDRESS";
const javaReplaced = {
isStatic: "PLAN_EXPORTED_VERSION",
address: "PLAN_BASE_ADDRESS"
}
const isCurrentAddress = (address) => {
const is = window.location.href.startsWith(address);
@ -8,7 +11,8 @@ const isCurrentAddress = (address) => {
return is;
}
export const baseAddress = "PLAN_BASE_ADDRESS" === toBeReplaced || !isCurrentAddress(toBeReplaced) ? "" : toBeReplaced;
export const baseAddress = "PLAN_BASE" + "_ADDRESS" === javaReplaced.address || !isCurrentAddress(javaReplaced.address) ? "" : javaReplaced.address;
export const staticSite = "PLAN_EXPORTED" + "_VERSION" !== javaReplaced.isStatic;
export const doSomeGetRequest = async (url, statusOptions) => {
return doSomeRequest(url, statusOptions, async () => axios.get(url));