Add Network Calendar to Network Overview

Does the same as Server Calendar but for the whole network.

Affects issues:
- Close #3111
This commit is contained in:
Aurora Lahtela 2023-09-30 17:10:18 +03:00
parent 9886040de7
commit 621e0b6f6e
11 changed files with 146 additions and 3 deletions

View File

@ -35,6 +35,7 @@ public enum WebPermission implements Supplier<String>, Lang {
PAGE_NETWORK_OVERVIEW_GRAPHS_ONLINE("See Players Online graph"),
PAGE_NETWORK_OVERVIEW_GRAPHS_DAY_BY_DAY("See Day by Day graph"),
PAGE_NETWORK_OVERVIEW_GRAPHS_HOUR_BY_HOUR("See Hour by Hour graph"),
PAGE_NETWORK_OVERVIEW_GRAPHS_CALENDAR("See Network calendar"),
PAGE_NETWORK_SERVER_LIST("See list of servers"),
PAGE_NETWORK_PLAYERBASE("See Playerbase Overview -tab"),
PAGE_NETWORK_PLAYERBASE_OVERVIEW("See Playerbase Overview numbers"),

View File

@ -126,6 +126,7 @@ public class NetworkPageExporter extends FileExporter {
"graph?type=activity",
"graph?type=geolocation",
"graph?type=uniqueAndNew",
"graph?type=serverCalendar",
"network/pingTable",
"sessions",
"extensionData?server=" + serverUUID,

View File

@ -310,6 +310,33 @@ public class GraphJSONCreator {
",\"firstDay\":" + 1 + '}';
}
public String networkCalendarJSON() {
Database db = dbSystem.getDatabase();
long now = System.currentTimeMillis();
long twoYearsAgo = now - TimeUnit.DAYS.toMillis(730L);
int timeZoneOffset = config.getTimeZone().getOffset(now);
NavigableMap<Long, Integer> uniquePerDay = db.query(
PlayerCountQueries.uniquePlayerCounts(twoYearsAgo, now, timeZoneOffset)
);
NavigableMap<Long, Integer> newPerDay = db.query(
PlayerCountQueries.newPlayerCounts(twoYearsAgo, now, timeZoneOffset)
);
NavigableMap<Long, Long> playtimePerDay = db.query(
SessionQueries.playtimePerDay(twoYearsAgo, now, timeZoneOffset)
);
NavigableMap<Long, Integer> sessionsPerDay = db.query(
SessionQueries.sessionCountPerDay(twoYearsAgo, now, timeZoneOffset)
);
return "{\"data\":" +
graphs.calendar().serverCalendar(
uniquePerDay,
newPerDay,
playtimePerDay,
sessionsPerDay
).toCalendarSeries() +
",\"firstDay\":" + 1 + '}';
}
public Map<String, Object> serverWorldPieJSONAsMap(ServerUUID serverUUID) {
Database db = dbSystem.getDatabase();
WorldTimes worldTimes = db.query(WorldTimesQueries.fetchServerTotalWorldTimes(serverUUID));

View File

@ -250,6 +250,8 @@ public class GraphsJSONResolver extends JSONResolver {
return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_DAY_BY_DAY);
case GRAPH_HOURLY_UNIQUE_NEW:
return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_HOUR_BY_HOUR);
case GRAPH_CALENDAR:
return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_CALENDAR);
case GRAPH_SERVER_PIE:
return List.of(WebPermission.PAGE_NETWORK_SESSIONS_SERVER_PIE);
case GRAPH_WORLD_MAP:
@ -313,6 +315,8 @@ public class GraphsJSONResolver extends JSONResolver {
return graphJSON.uniqueAndNewGraphJSON();
case GRAPH_HOURLY_UNIQUE_NEW:
return graphJSON.hourlyUniqueAndNewGraphJSON();
case GRAPH_CALENDAR:
return graphJSON.networkCalendarJSON();
case GRAPH_SERVER_PIE:
return graphJSON.serverPreferencePieJSONAsMap();
case GRAPH_HOSTNAME_PIE:

View File

@ -215,6 +215,7 @@ public enum HtmlLang implements Lang {
// React
LABEL_TITLE_SESSION_CALENDAR("html.label.sessionCalendar", "Session Calendar"),
LABEL_TITLE_SERVER_CALENDAR("html.label.serverCalendar", "Server Calendar"),
LABEL_TITLE_NETWORK_CALENDAR("html.label.networkCalendar", "Network Calendar"),
LABEL_LABEL_JOIN_ADDRESS("html.label.joinAddress", "Join Address"),
LABEL_LABEL_SESSION_MEDIAN("html.label.medianSessionLength", "Median Session Length"),
LABEL_LABEL_KDR("html.label.kdr", "KDR"),

View File

@ -452,6 +452,46 @@ public class SessionQueries {
};
}
/**
* Query session count for each day within range across the whole network.
*
* @param after After epoch ms
* @param before Before epoch ms
* @param timeZoneOffset Offset in ms to determine start of day.
* @return Map - Epoch ms (Start of day at 0 AM, no offset) : Session count of that day
*/
public static Query<NavigableMap<Long, Integer>> sessionCountPerDay(long after, long before, long timeZoneOffset) {
return database -> {
Sql sql = database.getSql();
String selectSessionsPerDay = SELECT +
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
"*1000 as date," +
"COUNT(1) as session_count" +
FROM + SessionsTable.TABLE_NAME +
WHERE + SessionsTable.SESSION_END + "<=?" +
AND + SessionsTable.SESSION_START + ">=?" +
GROUP_BY + "date";
return database.query(new QueryStatement<NavigableMap<Long, Integer>>(selectSessionsPerDay, 100) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setLong(1, timeZoneOffset);
statement.setLong(2, before);
statement.setLong(3, after);
}
@Override
public NavigableMap<Long, Integer> processResults(ResultSet set) throws SQLException {
NavigableMap<Long, Integer> uniquePerDay = new TreeMap<>();
while (set.next()) {
uniquePerDay.put(set.getLong("date"), set.getInt("session_count"));
}
return uniquePerDay;
}
});
};
}
public static Query<Long> playtime(long after, long before, ServerUUID serverUUID) {
String sql = SELECT + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime" +
FROM + SessionsTable.TABLE_NAME +
@ -562,6 +602,46 @@ public class SessionQueries {
};
}
/**
* Query playtime for each day within range across the whole network.
*
* @param after After epoch ms
* @param before Before epoch ms
* @param timeZoneOffset Offset in ms to determine start of day.
* @return Map - Epoch ms (Start of day at 0 AM, no offset) : Playtime of that day
*/
public static Query<NavigableMap<Long, Long>> playtimePerDay(long after, long before, long timeZoneOffset) {
return database -> {
Sql sql = database.getSql();
String selectPlaytimePerDay = SELECT +
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
"*1000 as date," +
"SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime" +
FROM + SessionsTable.TABLE_NAME +
WHERE + SessionsTable.SESSION_END + "<=?" +
AND + SessionsTable.SESSION_START + ">=?" +
GROUP_BY + "date";
return database.query(new QueryStatement<NavigableMap<Long, Long>>(selectPlaytimePerDay, 100) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setLong(1, timeZoneOffset);
statement.setLong(2, before);
statement.setLong(3, after);
}
@Override
public NavigableMap<Long, Long> processResults(ResultSet set) throws SQLException {
NavigableMap<Long, Long> uniquePerDay = new TreeMap<>();
while (set.next()) {
uniquePerDay.put(set.getLong("date"), set.getLong("playtime"));
}
return uniquePerDay;
}
});
};
}
public static Query<Long> averagePlaytimePerDay(long after, long before, long timeZoneOffset, ServerUUID serverUUID) {
return database -> {
Sql sql = database.getSql();

View File

@ -115,6 +115,7 @@ class AccessControlTest {
Arguments.of("/v1/graph?type=playersOnline&server=" + TestConstants.SERVER_UUID_STRING + "", WebPermission.PAGE_SERVER_OVERVIEW_PLAYERS_ONLINE_GRAPH, 200, 403),
Arguments.of("/v1/graph?type=uniqueAndNew", WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_DAY_BY_DAY, 200, 403),
Arguments.of("/v1/graph?type=hourlyUniqueAndNew", WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_HOUR_BY_HOUR, 200, 403),
Arguments.of("/v1/graph?type=serverCalendar", WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_CALENDAR, 200, 403),
Arguments.of("/v1/graph?type=serverPie", WebPermission.PAGE_NETWORK_SESSIONS_SERVER_PIE, 200, 403),
Arguments.of("/v1/graph?type=joinAddressPie", WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE, 200, 403),
Arguments.of("/v1/graph?type=activity", WebPermission.PAGE_NETWORK_PLAYERBASE_GRAPHS, 200, 403),

View File

@ -155,6 +155,7 @@ class AccessControlVisibilityTest {
Arguments.arguments(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_ONLINE, "online-activity-nav", "overview"),
Arguments.arguments(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_DAY_BY_DAY, "day-by-day-nav", "overview"),
Arguments.arguments(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_HOUR_BY_HOUR, "hour-by-hour-nav", "overview"),
Arguments.arguments(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_CALENDAR, "network-calendar-nav", "overview"),
Arguments.arguments(WebPermission.PAGE_NETWORK_OVERVIEW, "recent-players", "overview"),
Arguments.arguments(WebPermission.PAGE_NETWORK_OVERVIEW, "network-as-numbers", "overview"),
Arguments.arguments(WebPermission.PAGE_NETWORK_OVERVIEW, "week-comparison", "overview"),

View File

@ -21,8 +21,8 @@
<property name="headerFile" value="${config_loc}/java.header"/>
</module>
<module name="FileLength">
<!-- This value is alright. Notable: SessionQueries 808 -->
<property name="max" value="1000"/>
<!-- This value is alright. Notable: SessionQueries 1007 -->
<property name="max" value="1250"/>
<property name="fileExtensions" value=".java"/>
</module>
<module name="LineLength">

View File

@ -4,7 +4,12 @@ import {Card} from "react-bootstrap";
import CardTabs from "../../../CardTabs";
import {faChartArea} from "@fortawesome/free-solid-svg-icons";
import {useDataRequest} from "../../../../hooks/dataFetchHook";
import {fetchDayByDayGraph, fetchHourByHourGraph, fetchPlayersOnlineGraph} from "../../../../service/serverService";
import {
fetchDayByDayGraph,
fetchHourByHourGraph,
fetchNetworkCalendarGraph,
fetchPlayersOnlineGraph
} from "../../../../service/serverService";
import {ErrorViewBody} from "../../../../views/ErrorView";
import {ChartLoader} from "../../../navigation/Loader";
import TimeByTimeGraph from "../../../graphs/TimeByTimeGraph";
@ -12,6 +17,8 @@ import PlayersOnlineGraph from "../../../graphs/PlayersOnlineGraph";
import {useMetadata} from "../../../../hooks/metadataHook";
import StackedPlayersOnlineGraph from "../../../graphs/StackedPlayersOnlineGraph";
import {useAuth} from "../../../../hooks/authenticationHook";
import {faCalendar} from "@fortawesome/free-regular-svg-icons";
import ServerCalendar from "../../../calendar/ServerCalendar";
const SingleProxyPlayersOnlineGraph = ({serverUUID}) => {
const {data, loadingError} = useDataRequest(fetchPlayersOnlineGraph, [serverUUID]);
@ -61,6 +68,16 @@ const HourByHourTab = () => {
return <TimeByTimeGraph id={"hour-by-hour-graph"} data={data}/>
}
const NetworkCalendarTab = () => {
const {data, loadingError} = useDataRequest(fetchNetworkCalendarGraph, [])
if (loadingError) return <ErrorViewBody error={loadingError}/>
if (!data) return <ChartLoader/>;
return <ServerCalendar series={data.data} firstDay={data.firstDay}/>
}
const NetworkOnlineActivityGraphsCard = () => {
const {hasPermission} = useAuth();
const {t} = useTranslation();
@ -77,6 +94,10 @@ const NetworkOnlineActivityGraphsCard = () => {
name: t('html.label.hourByHour'), icon: faChartArea, color: 'blue', href: 'hour-by-hour',
element: <HourByHourTab/>,
permission: 'page.network.overview.graphs.hour.by.hour'
}, {
name: t('html.label.networkCalendar'), icon: faCalendar, color: 'teal', href: 'network-calendar',
element: <NetworkCalendarTab/>,
permission: 'page.network.overview.graphs.calendar'
}
].filter(tab => hasPermission(tab.permission));
return <Card>

View File

@ -204,6 +204,12 @@ export const fetchServerCalendarGraph = async (timestamp, identifier) => {
return doGetRequest(url, timestamp);
}
export const fetchNetworkCalendarGraph = async (timestamp) => {
let url = `/v1/graph?type=serverCalendar`;
if (staticSite) url = `/data/graph-serverCalendar.json`;
return doGetRequest(url, timestamp);
}
export const fetchPunchCardGraph = async (timestamp, identifier) => {
let url = `/v1/graph?type=punchCard&server=${identifier}`;
if (staticSite) url = `/data/graph-punchCard_${identifier}.json`;