Punchcard for inspect page & Bugfixes

Fix #90
This commit is contained in:
Rsl1122 2017-05-22 00:39:32 +03:00
parent 4345b30f78
commit f69f9ee372
13 changed files with 380 additions and 41 deletions

View File

@ -68,6 +68,7 @@ public enum Settings {
HIDE_TOWNS("Customization.Plugins.Towny.HideTowns");
private final String configPath;
private Boolean value;
private Settings(String path) {
this.configPath = path;
@ -79,9 +80,16 @@ public enum Settings {
* @return Boolean value of the config setting, false if not boolean.
*/
public boolean isTrue() {
if (value != null) {
return value;
}
return Plan.getInstance().getConfig().getBoolean(configPath);
}
public void setValue(Boolean value) {
this.value = value;
}
/**
* If the settings is a String, this method should be used.
*

View File

@ -1,5 +1,6 @@
package main.java.com.djrapitops.plan.data;
import java.util.Objects;
import java.util.UUID;
/**
@ -65,4 +66,35 @@ public class KillData {
public int getVictimUserID() {
return victimUserID;
}
@Override
public String toString() {
return "{victim:" + victim + "|victimUserID:" + victimUserID + "|date:" + date + "|weapon:" + weapon + '}';
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final KillData other = (KillData) obj;
if (this.date != other.date) {
return false;
}
if (!Objects.equals(this.weapon, other.weapon)) {
return false;
}
if (!Objects.equals(this.victim, other.victim)) {
return false;
}
return true;
}
}

View File

@ -41,16 +41,16 @@ public class EconomyBalance extends PluginData {
@Override
public String getHtmlReplaceValue(String modifierPrefix, UUID uuid) {
OfflinePlayer p = getOfflinePlayer(uuid);
if (p.hasPlayedBefore()) {
parseContainer(modifierPrefix, this.econ.getBalance(p) + "");
if (this.econ.hasAccount(p)) {
return parseContainer(modifierPrefix, this.econ.getBalance(p) + "");
}
return "";
return parseContainer(modifierPrefix, "0");
}
@Override
public Serializable getValue(UUID uuid) {
OfflinePlayer p = getOfflinePlayer(uuid);
if (p.hasPlayedBefore()) {
if (this.econ.hasAccount(p)) {
return this.econ.getBalance(p);
}
return -1;

View File

@ -345,15 +345,16 @@ public abstract class SQLDB extends Database {
UserData uData = userDatas.get(uuid);
if (id == -1) {
saveLast.add(uData);
Log.debug("User not seen before, saving last: "+uuid);
continue;
}
uData.access();
locations.put(id, uData.getLocations());
nicknames.put(id, uData.getNicknames());
locations.put(id, new ArrayList<>(uData.getLocations()));
nicknames.put(id, new HashSet<>(uData.getNicknames()));
lastNicks.put(id, uData.getLastNick());
ips.put(id, uData.getIps());
kills.put(id, uData.getPlayerKills());
sessions.put(id, uData.getSessions());
ips.put(id, new HashSet<>(uData.getIps()));
kills.put(id, new ArrayList<>(uData.getPlayerKills()));
sessions.put(id, new ArrayList<>(uData.getSessions()));
gmTimes.put(id, uData.getGmTimes());
}
// Save
@ -378,9 +379,8 @@ public abstract class SQLDB extends Database {
for (UserData userData : saveLast) {
try {
saveUserData(userData);
} catch (SQLException e) {
} catch (SQLException | NullPointerException e) {
exceptions.add(e);
} catch (NullPointerException e) {
}
}
if (!exceptions.isEmpty()) {
@ -408,11 +408,11 @@ public abstract class SQLDB extends Database {
data.access();
usersTable.saveUserDataInformation(data);
int userId = usersTable.getUserId(uuid.toString());
sessionsTable.saveSessionData(userId, data.getSessions());
sessionsTable.saveSessionData(userId, new ArrayList<>(data.getSessions()));
locationsTable.saveAdditionalLocationsList(userId, data.getLocations());
nicknamesTable.saveNickList(userId, data.getNicknames(), data.getLastNick());
ipsTable.saveIPList(userId, data.getIps());
killsTable.savePlayerKills(userId, data.getPlayerKills());
nicknamesTable.saveNickList(userId, new HashSet<>(data.getNicknames()), data.getLastNick());
ipsTable.saveIPList(userId, new HashSet<>(data.getIps()));
killsTable.savePlayerKills(userId, new ArrayList<>(data.getPlayerKills()));
gmTimesTable.saveGMTimes(userId, data.getGmTimes());
data.stopAccessing();
}

View File

@ -184,9 +184,11 @@ public class KillsTable extends Table {
public void savePlayerKills(Map<Integer, List<KillData>> kills, Map<Integer, UUID> uuids) throws SQLException {
if (kills == null || kills.isEmpty()) {
Log.debug("Save multiple - Kills was empty.");
return;
}
Map<Integer, List<KillData>> saved = getPlayerKills(kills.keySet(), uuids);
PreparedStatement statement = null;
try {
statement = prepareStatement("INSERT INTO " + tableName + " ("
@ -199,6 +201,7 @@ public class KillsTable extends Table {
for (Integer id : kills.keySet()) {
List<KillData> playerKills = kills.get(id);
List<KillData> s = saved.get(id);
Log.debug("Saving:" + playerKills + " Saved: " + s);
if (s != null) {
playerKills.removeAll(s);
}

View File

@ -0,0 +1,128 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package main.java.com.djrapitops.plan.ui.graphs;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import main.java.com.djrapitops.plan.Log;
import main.java.com.djrapitops.plan.data.SessionData;
/**
*
* @author Rsl1122
*/
public class PunchCardGraphCreator {
public static String generateDataArray(Collection<SessionData> data) {
// Initialize dataset
List<Long> sessionStarts = getSessionStarts(data);
List<int[]> daysAndHours = getDaysAndHours(sessionStarts);
int[][] dataArray = createDataArray(daysAndHours);
int big = findBiggestValue(dataArray);
int[][] scaled = scale(dataArray, big);
StringBuilder arrayBuilder = new StringBuilder();
arrayBuilder.append("[");
arrayBuilder.append("{").append("x:").append(-1).append(", y:").append(-1).append(", r:").append(1).append("}");
arrayBuilder.append(",");
arrayBuilder.append("{").append("x:").append(25).append(", y:").append(7).append(", r:").append(1).append("}");
arrayBuilder.append(",");
for (int i = 0; i < 7; i++) {
for (int j = 0; j < 24; j++) {
int value = scaled[i][j];
if (value == 0) {
continue;
}
arrayBuilder.append("{").append("x:").append(j).append(", y:").append(i).append(", r:").append(value).append("}");
if (!(i == 6 && j == 23)) {
arrayBuilder.append(",");
}
}
}
arrayBuilder.append("]");
return arrayBuilder.toString();
}
private static int[][] createDataArray(List<int[]> daysAndHours) {
int[][] dataArray = createEmptyArray();
for (int[] dAndH : daysAndHours) {
int d = dAndH[0];
int h = dAndH[1];
dataArray[d][h] = dataArray[d][h] + 1;
}
return dataArray;
}
private static List<int[]> getDaysAndHours(List<Long> sessionStarts) {
List<int[]> daysAndHours = sessionStarts.stream().map(start -> {
Calendar day = Calendar.getInstance();
day.setTimeInMillis(start);
int dayOfWeek = day.get(Calendar.DAY_OF_WEEK) - 2;
if (dayOfWeek == -1) {
dayOfWeek = 6;
}
int hourOfDay = day.get(Calendar.HOUR_OF_DAY);
if (hourOfDay == 24) {
hourOfDay = 0;
}
return new int[]{dayOfWeek, hourOfDay};
}).collect(Collectors.toList());
return daysAndHours;
}
private static List<Long> getSessionStarts(Collection<SessionData> data) {
List<Long> sessionStarts = data.stream()
.filter(s -> s != null)
.filter(s -> s.isValid())
.map(s -> s.getSessionStart())
.sorted()
.collect(Collectors.toList());
return sessionStarts;
}
private static int[][] createEmptyArray() {
int[][] dataArray = new int[7][24];
for (int i = 0; i < 7; i++) {
for (int j = 0; j < 24; j++) {
dataArray[i][j] = 0;
}
}
return dataArray;
}
private static int findBiggestValue(int[][] dataArray) {
int highest = 1;
for (int i = 0; i < 7; i++) {
for (int j = 0; j < 24; j++) {
int num = dataArray[i][j];
if (num > highest) {
highest = num;
}
}
}
return highest;
}
private static int[][] scale(int[][] dataArray, int big) {
int[][] scaled = new int[7][24];
for (int i = 0; i < 7; i++) {
Log.debug("Scaling: " + Arrays.toString(dataArray[i]) + " | " + big);
for (int j = 0; j < 24; j++) {
int value = (int) ((dataArray[i][j] * 10.0) / big);
if (value != 0) {
value += 4;
}
scaled[i][j] = value;
}
Log.debug(" Scaled: " + Arrays.toString(scaled[i]));
}
return scaled;
}
}

View File

@ -29,7 +29,7 @@ public class AnalysisUtils {
public static boolean isActive(long lastPlayed, long playTime, int loginTimes) {
return isActive(MiscUtils.getTime(), lastPlayed, playTime, loginTimes);
}
public static boolean isActive(long now, long lastPlayed, long playTime, int loginTimes) {
int timeToActive = Settings.ANALYSIS_MINUTES_FOR_ACTIVE.getNumber();
if (timeToActive < 0) {
@ -134,28 +134,28 @@ public class AnalysisUtils {
return source.parseContainer("Err ", "Null Analysistype. ");
}
try {
Number average;
double average;
switch (analysisType) {
case LONG_EPOCH_MS_MINUS_NOW_AVG:
final long now = MiscUtils.getTime();
average = MathUtils.averageLong(getCorrectValues(uuids, source).map(value -> ((long) value) - now));
return source.parseContainer(analysisType.getModifier(), FormatUtils.formatTimeAmount((long) average));
case INT_AVG:
average = MathUtils.averageInt(getCorrectValues(uuids, source).map(i -> (Integer) i));
break;
case LONG_AVG:
average = MathUtils.averageLong(getCorrectValues(uuids, source).map(i -> (Long) i));
break;
long averageLong = MathUtils.averageLong(getCorrectValues(uuids, source).map(i -> (Long) i));
return source.parseContainer(analysisType.getModifier(), averageLong + "");
case LONG_TIME_MS_AVG:
average = MathUtils.averageLong(getCorrectValues(uuids, source).map(i -> (Long) i));
return source.parseContainer(analysisType.getModifier(), FormatUtils.formatTimeAmount((long) average));
case INT_AVG:
average = MathUtils.averageInt(getCorrectValues(uuids, source).map(i -> (Integer) i));
break;
case DOUBLE_AVG:
average = MathUtils.averageDouble(getCorrectValues(uuids, source).map(i -> (Double) i));
break;
default:
return source.parseContainer("Err ", "Wrong Analysistype specified: " + analysisType.name());
}
return source.parseContainer(analysisType.getModifier(), average + "");
return source.parseContainer(analysisType.getModifier(), FormatUtils.cutDecimals(average));
} catch (Throwable e) {
return logPluginDataCausedError(source, e);
}
@ -197,4 +197,4 @@ public class AnalysisUtils {
Log.toLog("com.djrapitops.plan.utilities.AnalysisUtils", e);
return source.parseContainer("", "Exception during calculation.");
}
}
}

View File

@ -12,6 +12,7 @@ import main.java.com.djrapitops.plan.data.AnalysisData;
import main.java.com.djrapitops.plan.data.UserData;
import main.java.com.djrapitops.plan.ui.Html;
import main.java.com.djrapitops.plan.ui.graphs.PlayerActivityGraphCreator;
import main.java.com.djrapitops.plan.ui.graphs.PunchCardGraphCreator;
import main.java.com.djrapitops.plan.ui.tables.SortableKillsTableCreator;
import main.java.com.djrapitops.plan.ui.tables.SortableSessionTableCreator;
import org.bukkit.GameMode;
@ -197,6 +198,7 @@ public class PlaceholderUtils {
replaceMap.put("%gm1col%", Settings.HCOLOR_GMP_1 + "");
replaceMap.put("%gm2col%", Settings.HCOLOR_GMP_2 + "");
replaceMap.put("%gm3col%", Settings.HCOLOR_GMP_3 + "");
replaceMap.put("%datapunchcard%", PunchCardGraphCreator.generateDataArray(data.getSessions()));
replaceMap.put("%inaccuratedatawarning%", (now - data.getRegistered() < 180000) ? Html.WARN_INACCURATE.parse() : "");
String pluginsTabHtml = plugin.getHookHandler().getPluginsTabLayoutForInspect();
Map<String, String> additionalReplaceRules = plugin.getHookHandler().getAdditionalInspectReplaceRules(uuid);

View File

@ -288,6 +288,9 @@ table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sor
<a href="javascript:void(0)" class="nav-button">
<i class="fa fa-info-circle" aria-hidden="true"></i> Information
</a>
<a href="javascript:void(0)" class="nav-button">
<i class="fa fa-calendar" aria-hidden="true"></i> Sessions
</a>
<a href="javascript:void(0)" class="nav-button">
<i class="fa fa-cubes" aria-hidden="true"></i> Plugins
</a>
@ -379,18 +382,18 @@ table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sor
<div class="about box column">
<div class="headerbox">
<div class="header-icon">
<div class="header-label"><i class="fa fa-list" aria-hidden="true"></i><span class="header-text"> Last 10 Sessions</span></div>
<div class="header-label"><i class="fa fa-crosshairs" aria-hidden="true"></i><span class="header-text"> Last 10 Kills</span></div>
</div>
<div class="infobox">
<div class="infobox" style="float: right;">
<div class="info-icon">
<i class="fa fa-clock-o" aria-hidden="true"></i>
<i class="fa fa-crosshairs" aria-hidden="true"></i>
</div>
<div class="info-text" style="width: 70%;">
<div class="info-number">
%sessionaverage%
%playerkills%
</div>
<div class="info-label">
Average Length
Player Kills
</div>
</div>
</div>
@ -456,26 +459,59 @@ table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sor
<canvas id="gmPie" width="1000" height="600" style="width: 95%;"></canvas>
</div>
<div class="about box column">
%sessionstable%<br>
%killstable%
</div>
</div>
</div>
<div class="tab">
<div class="columns">
<div class="about box column">
<div class="headerbox">
<div class="header-icon">
<div class="header-label"><i class="fa fa-crosshairs" aria-hidden="true"></i><span class="header-text"> Last 10 Kills</span></div>
<div class="header-icon" style="width: 50%">
<div class="header-label"><i class="fa fa-pie-chart" aria-hidden="true"></i><span class="header-text"> PunchCard</span></div>
</div>
<div class="infobox" style="float: right;">
<div class="infobox" >
<div class="info-icon">
<i class="fa fa-crosshairs" aria-hidden="true"></i>
<i class="fa fa-clock-o" aria-hidden="true"></i>
</div>
<div class="info-text" style="width: 70%;">
<div class="info-number">
%playerkills%
%playtime%
</div>
<div class="info-label">
Player Kills
Playtime
</div>
</div>
</div>
</div><br/>
%killstable%
</div>
</div>
<div class="about box column">
<div class="headerbox">
<div class="header-icon">
<div class="header-label"><i class="fa fa-list" aria-hidden="true"></i><span class="header-text"> Last 10 Sessions</span></div>
</div>
<div class="infobox">
<div class="info-icon">
<i class="fa fa-clock-o" aria-hidden="true"></i>
</div>
<div class="info-text" style="width: 70%;">
<div class="info-number">
%sessionaverage%
</div>
<div class="info-label">
Average Length
</div>
</div>
</div>
</div>
</div>
</div>
<div class="columns">
<div class="about box column">
<canvas id="punchcard" width="1000" height="600" style="width: 95%;"></canvas>
</div>
<div class="about box column">
%sessionstable%
</div>
</div>
</div>
@ -579,6 +615,66 @@ function openFunc(i) {
}
}
});
var ctxpunch = document.getElementById("punchcard");
var datapunch = {
datasets: [
{
label: 'Player Join Punchcard',
data: %datapunchcard%,
backgroundColor: "#222",
hoverBackgroundColor: "#333",
}]
};
var punchcardChart = new Chart(ctxpunch, {
type: 'bubble',
data: datapunch,
options: {
scales: {
xAxes: [{
ticks: {
callback: function(value, index, values) {
switch (value) {
case 25:
case -1:
case -2:
case -3:
case -4:
case -5:
return '';
default:
return value+':00';
}
}
}
}],
yAxes: [{
ticks: {
callback: function(value, index, values) {
switch (value) {
case 0:
return 'Monday';
case 1:
return 'Tuesday';
case 2:
return 'Wednesday';
case 3:
return 'Thursday';
case 4:
return 'Friday';
case 5:
return 'Saturday';
case 6:
return 'Sunday';
default:
break;
return '';
}
}
}
}]
}
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,34 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package test.java.main.java.com.djrapitops.plan;
import main.java.com.djrapitops.plan.Permissions;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import test.java.utils.MockUtils;
/**
*
* @author Risto
*/
public class PermissionsTest {
public PermissionsTest() {
}
@Test
public void testUserHasThisPermission() {
assertTrue(Permissions.INSPECT_OTHER.userHasThisPermission(MockUtils.mockPlayer()));
}
@Test
public void testGetPermission() {
assertEquals("plan.inspect.other", Permissions.INSPECT_OTHER.getPermission());
}
}

View File

@ -5,6 +5,8 @@
*/
package test.java.main.java.com.djrapitops.plan;
import java.util.ArrayList;
import java.util.List;
import main.java.com.djrapitops.plan.Settings;
import org.bukkit.plugin.java.JavaPlugin;
import static org.junit.Assert.*;
@ -62,6 +64,14 @@ public class SettingsTest {
assertEquals(8804, Settings.WEBSERVER_PORT.getNumber());
}
@Test
public void testGetStringList() {
List<String> exp = new ArrayList<>();
exp.add("ExampleTown");
List<String> result = Settings.HIDE_TOWNS.getStringList();
assertEquals(exp, result);
}
/**
*
*/

View File

@ -7,6 +7,8 @@ package test.java.main.java.com.djrapitops.plan.database;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.sql.SQLException;
@ -20,6 +22,8 @@ import java.util.UUID;
import java.util.stream.Collectors;
import main.java.com.djrapitops.plan.Plan;
import main.java.com.djrapitops.plan.data.DemographicsData;
import main.java.com.djrapitops.plan.data.KillData;
import main.java.com.djrapitops.plan.data.SessionData;
import main.java.com.djrapitops.plan.data.UserData;
import main.java.com.djrapitops.plan.data.cache.DBCallableProcessor;
import main.java.com.djrapitops.plan.database.Database;
@ -78,7 +82,7 @@ public class DatabaseTest {
public void startConnectionPingTask(Plan plugin) {
}
};
};
File f = new File(plan.getDataFolder(), "Errors.txt");
rows = 0;
if (f.exists()) {
@ -258,21 +262,39 @@ public class DatabaseTest {
/**
*
* @throws SQLException
* @throws java.net.UnknownHostException
*/
@Test
public void testSaveMultipleUserData() throws SQLException {
public void testSaveMultipleUserData() throws SQLException, UnknownHostException {
db.init();
UserData data = new UserData(MockUtils.mockPlayer(), new DemographicsData());
data.addIpAddress(InetAddress.getByName("185.64.113.61"));
data.addSession(new SessionData(1286349L, 2342978L));
data.addNickname("TestNick");
data.addPlayerKill(new KillData(MockUtils.getPlayer2UUID(), 2, "DiamondSword", 75843759L));
System.out.println(data.toString());
db.saveUserData(data);
data.getPlayerKills().clear();
System.out.println(data.toString());
data.addNickname("TestUpdateForSave");
UserData data2 = new UserData(MockUtils.mockPlayer2(), new DemographicsData());
data2.addNickname("Alright");
data.addNickname("TestNick2");
data2.addIpAddress(InetAddress.getByName("185.64.113.60"));
data2.addSession(new SessionData(2348743L, 4839673L));
data2.addPlayerKill(new KillData(MockUtils.getPlayerUUID(), 1, "DiamondSword", 753759L));
List<UserData> list = new ArrayList<>();
list.add(data);
list.add(data2);
db.saveMultipleUserData(list);
data.addPlayerKill(new KillData(MockUtils.getPlayer2UUID(), 2, "DiamondSword", 75843759L));
data.setLocation(null);
data2.setLocation(null);
DBCallableProcessor process = new DBCallableProcessor() {
@Override
public void process(UserData d) {
System.out.println("\n" + data.toString());
System.out.println(d.toString());
assertTrue("Not Equals", data.equals(d));
}
};
@ -280,6 +302,8 @@ public class DatabaseTest {
DBCallableProcessor process2 = new DBCallableProcessor() {
@Override
public void process(UserData d) {
System.out.println("\n" + data2.toString());
System.out.println(d.toString());
assertTrue("Not Equals", data2.equals(d));
}
};

View File

@ -10,6 +10,7 @@ import java.io.FileInputStream;
import java.nio.file.Files;
import java.util.logging.Logger;
import main.java.com.djrapitops.plan.Plan;
import main.java.com.djrapitops.plan.Settings;
import org.bukkit.Server;
import org.bukkit.configuration.file.YamlConfiguration;
import org.powermock.api.mockito.PowerMockito;
@ -63,7 +64,8 @@ public class TestInit {
when(planMock.getServer()).thenReturn(mockServer);
when(planMock.getLogger()).thenReturn(Logger.getGlobal());
Plan.setInstance(planMock);
// Mockito.doReturn("0.0.0.0").when(planMock).getServer().getIp();
// Mockito.doReturn("0.0.0.0").when(planMock).getServer().getIp();
Settings.DEBUG.setValue(true);
return true;
} catch (Exception ex) {
System.out.println(ex);