Added activity graphs to query results

This commit is contained in:
Risto Lahtela 2021-01-19 09:54:49 +02:00
parent c1d53c27d3
commit a3c16b4b21
4 changed files with 105 additions and 8 deletions

View File

@ -16,8 +16,10 @@
*/ */
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.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;
import com.djrapitops.plan.delivery.rendering.json.graphs.GraphJSONCreator;
import com.djrapitops.plan.delivery.web.resolver.MimeType; import com.djrapitops.plan.delivery.web.resolver.MimeType;
import com.djrapitops.plan.delivery.web.resolver.Resolver; import com.djrapitops.plan.delivery.web.resolver.Resolver;
import com.djrapitops.plan.delivery.web.resolver.Response; import com.djrapitops.plan.delivery.web.resolver.Response;
@ -30,12 +32,14 @@ import com.djrapitops.plan.settings.config.paths.TimeSettings;
import com.djrapitops.plan.settings.locale.Locale; 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.Database; import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.queries.analysis.NetworkActivityIndexQueries;
import com.djrapitops.plan.storage.database.queries.filter.Filter; import com.djrapitops.plan.storage.database.queries.filter.Filter;
import com.djrapitops.plan.storage.database.queries.filter.FilterQuery; import com.djrapitops.plan.storage.database.queries.filter.FilterQuery;
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.objects.playertable.QueryTablePlayersQuery; import com.djrapitops.plan.storage.database.queries.objects.playertable.QueryTablePlayersQuery;
import com.djrapitops.plan.storage.json.JSONStorage; import com.djrapitops.plan.storage.json.JSONStorage;
import com.djrapitops.plan.utilities.java.Maps; import com.djrapitops.plan.utilities.java.Maps;
import com.djrapitops.plugin.api.TimeAmount;
import com.google.gson.Gson; import com.google.gson.Gson;
import javax.inject.Inject; import javax.inject.Inject;
@ -54,6 +58,7 @@ public class QueryJSONResolver implements Resolver {
private final PlanConfig config; private final PlanConfig config;
private final DBSystem dbSystem; private final DBSystem dbSystem;
private final JSONStorage jsonStorage; private final JSONStorage jsonStorage;
private final GraphJSONCreator graphJSONCreator;
private final Locale locale; private final Locale locale;
private final Formatters formatters; private final Formatters formatters;
@ -63,6 +68,7 @@ public class QueryJSONResolver implements Resolver {
PlanConfig config, PlanConfig config,
DBSystem dbSystem, DBSystem dbSystem,
JSONStorage jsonStorage, JSONStorage jsonStorage,
GraphJSONCreator graphJSONCreator,
Locale locale, Locale locale,
Formatters formatters Formatters formatters
) { ) {
@ -70,6 +76,7 @@ public class QueryJSONResolver implements Resolver {
this.config = config; this.config = config;
this.dbSystem = dbSystem; this.dbSystem = dbSystem;
this.jsonStorage = jsonStorage; this.jsonStorage = jsonStorage;
this.graphJSONCreator = graphJSONCreator;
this.locale = locale; this.locale = locale;
this.formatters = formatters; this.formatters = formatters;
} }
@ -140,12 +147,34 @@ public class QueryJSONResolver implements Resolver {
Database database = dbSystem.getDatabase(); Database database = dbSystem.getDatabase();
return Maps.builder(String.class, Object.class) return Maps.builder(String.class, Object.class)
.put("players", new PlayersTableJSONCreator( .put("players", getPlayersTableData(playerUUIDs, after, before))
database.query(new QueryTablePlayersQuery(playerUUIDs, after, before, config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD))), .put("activity", getActivityGraphData(playerUUIDs, after, before))
Collections.emptyMap(),
config.get(DisplaySettings.OPEN_PLAYER_LINKS_IN_NEW_TAB),
formatters, locale
).toJSONMap())
.build(); .build();
} }
private Map<String, Object> getActivityGraphData(Set<UUID> playerUUIDs, long after, long before) {
Database database = dbSystem.getDatabase();
Long threshold = config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD);
long twoMonthsBeforeLastDate = before - TimeAmount.MONTH.toMillis(2L);
long stopDate = Math.max(twoMonthsBeforeLastDate, after);
DateMap<Map<String, Integer>> activityData = new DateMap<>();
for (long time = before; time >= stopDate; time -= TimeAmount.WEEK.toMillis(1L)) {
activityData.put(time, database.query(NetworkActivityIndexQueries.fetchActivityIndexGroupingsOn(time, threshold, playerUUIDs)));
}
Map<String, Object> activityGraphJSON = graphJSONCreator.createActivityGraphJSON(activityData);
return activityGraphJSON;
}
private Map<String, Object> getPlayersTableData(Set<UUID> playerUUIDs, long after, long before) {
Database database = dbSystem.getDatabase();
return new PlayersTableJSONCreator(
database.query(new QueryTablePlayersQuery(playerUUIDs, after, before, config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD))),
Collections.emptyMap(),
config.get(DisplaySettings.OPEN_PLAYER_LINKS_IN_NEW_TAB),
formatters, locale
).toJSONMap();
}
} }

View File

@ -21,6 +21,7 @@ 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;
import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import com.djrapitops.plan.storage.database.sql.tables.UsersTable;
import org.apache.commons.text.TextStringBuilder;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
@ -162,6 +163,36 @@ public class NetworkActivityIndexQueries {
}; };
} }
public static Query<Map<String, Integer>> fetchActivityIndexGroupingsOn(long date, long threshold, Collection<UUID> playerUUIDs) {
String selectActivityIndex = selectActivityIndexSQL();
String selectIndexes = SELECT + "activity_index" +
FROM + UsersTable.TABLE_NAME + " u" +
LEFT_JOIN + '(' + selectActivityIndex + ") s on s." + SessionsTable.USER_UUID + "=u." + UsersTable.USER_UUID +
WHERE + "u." + UsersTable.REGISTERED + "<=?" +
AND + "u." + UsersTable.USER_UUID + " IN ('" +
new TextStringBuilder().appendWithSeparators(playerUUIDs, "','").build() + "')";
return new QueryStatement<Map<String, Integer>>(selectIndexes) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
setSelectActivityIndexSQLParameters(statement, 1, threshold, date);
statement.setLong(9, date);
}
@Override
public Map<String, Integer> processResults(ResultSet set) throws SQLException {
Map<String, Integer> groups = new HashMap<>();
while (set.next()) {
double activityIndex = set.getDouble("activity_index");
String group = ActivityIndex.getGroup(activityIndex);
groups.put(group, groups.getOrDefault(group, 0) + 1);
}
return groups;
}
};
}
public static Query<Integer> countNewPlayersTurnedRegular(long after, long before, Long threshold) { public static Query<Integer> countNewPlayersTurnedRegular(long after, long before, Long threshold) {
String selectActivityIndex = selectActivityIndexSQL(); String selectActivityIndex = selectActivityIndexSQL();

View File

@ -303,19 +303,29 @@ function runQuery() {
renderDataResultScreen(json.data.players.data.length, json.view ? json.view : {}); renderDataResultScreen(json.data.players.data.length, json.view ? json.view : {});
// Set URL so that the query result can be shared
window.history.replaceState({}, '', `${location.pathname}?timestamp=${json.timestamp}`); window.history.replaceState({}, '', `${location.pathname}?timestamp=${json.timestamp}`);
// Player table
$('.player-table').DataTable({ $('.player-table').DataTable({
responsive: true, responsive: true,
columns: json.data.players.columns, columns: json.data.players.columns,
data: json.data.players.data, data: json.data.players.data,
order: [[5, "desc"]] order: [[5, "desc"]]
}) });
// Activity graphs
const activity_data = json.data.activity;
activityPie('activityPie', {
name: 'Players', colorByPoint: true, data: activity_data.activity_pie_series
});
stackChart('activityStackGraph', activity_data.activity_labels, activity_data.activity_series, 'Players');
const activityIndexHeader = document.querySelector("#DataTables_Table_0 thead th:nth-of-type(2)"); const activityIndexHeader = document.querySelector("#DataTables_Table_0 thead th:nth-of-type(2)");
const lastSeenHeader = document.querySelector("#DataTables_Table_0 thead th:nth-of-type(6)"); const lastSeenHeader = document.querySelector("#DataTables_Table_0 thead th:nth-of-type(6)");
activityIndexHeader.innerHTML += ` (${filterView.beforeDate})` document.querySelector("#activity-date").innerHTML = json.view.beforeDate;
activityIndexHeader.innerHTML += ` (${json.view.beforeDate})`
lastSeenHeader.innerHTML += ` (view)` lastSeenHeader.innerHTML += ` (view)`
}); });
} }
@ -351,5 +361,28 @@ function renderDataResultScreen(resultCount, view) {
</div> </div>
</div> </div>
</div> </div>
<div class="row">
<div class="col-xl-8 col-lg-8 col-sm-12">
<div class="card shadow mb-4">
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold col-black"><i
class="fas fa-fw fa-chart-line col-amber"></i>
Activity of matched players</h6>
</div>
<div class="chart-area" id="activityStackGraph"></div>
</div>
</div>
<div class="col-xl-4 col-lg-4 col-sm-12">
<div class="card shadow mb-4">
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold col-black"><i
class="fa fa-fw fa-users col-amber"></i>
Activity on <span id="activity-date"></span></h6>
</div>
<div class="chart-area" id="activityPie"></div>
</div>
</div>
</div>
</div>`; </div>`;
} }

View File

@ -316,8 +316,12 @@
<!-- Page level plugins --> <!-- Page level plugins -->
<script src="vendor/datatables/jquery.dataTables.min.js"></script> <script src="vendor/datatables/jquery.dataTables.min.js"></script>
<script src="vendor/datatables/dataTables.bootstrap4.min.js"></script> <script src="vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="./vendor/highcharts/highstock.js"></script>
<script src="./vendor/highcharts/map.js"></script>
<script src="./vendor/highcharts/world.js"></script>
<!-- Page level custom scripts --> <!-- Page level custom scripts -->
<script src="./js/graphs.js"></script>
<script src='./js/query.js'></script> <script src='./js/query.js'></script>
<script id="mainScript"> <script id="mainScript">