Implemented rest of the graphs on Online Activity Overview

This commit is contained in:
Aurora Lahtela 2022-05-21 13:43:49 +03:00
parent 538eff10d9
commit e77a9ee8b5
12 changed files with 218 additions and 112 deletions

View File

@ -37,6 +37,7 @@ public enum HtmlLang implements Lang {
SIDE_PERFORMANCE("html.label.performance", "Performance"),
QUERY_MAKE("html.label.query", "Make a query"),
UNIT_NO_DATA("generic.noData", "No Data"), // Generic
GRAPH_NO_DATA("html.label.noDataToDisplay", "No Data to Display"),
// Modals
TITLE_THEME_SELECT("html.label.themeSelect", "Theme Select"),
LINK_NIGHT_MODE("html.button.nightMode", "Night Mode"),

View File

@ -0,0 +1,28 @@
import React from "react";
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
const ServerCalendar = ({series, firstDay}) => {
return (
<FullCalendar
plugins={[dayGridPlugin]}
timeZone="UTC"
themeSystem='bootstrap'
eventColor='#2196F3'
// dayMaxEventRows={4}
firstDay={firstDay}
initialView='dayGridMonth'
navLinks={true}
height={800}
contentHeight={800}
headerToolbar={{
left: 'title',
center: '',
right: 'dayGridMonth dayGridWeek dayGridDay today prev next'
}}
events={(_fetchInfo, successCallback) => successCallback(series)}
/>
)
}
export default ServerCalendar

View File

@ -0,0 +1,85 @@
import {useParams} from "react-router-dom";
import {useDataRequest} from "../../../hooks/dataFetchHook";
import {
fetchDayByDayGraph,
fetchHourByHourGraph,
fetchPunchCardGraph,
fetchServerCalendarGraph
} from "../../../service/serverService";
import {ErrorViewBody} from "../../../views/ErrorView";
import PunchCard from "../../graphs/PunchCard";
import {useTranslation} from "react-i18next";
import {Card} from "react-bootstrap-v5";
import CardTabs from "../../CardTabs";
import {faBraille, faChartArea} from "@fortawesome/free-solid-svg-icons";
import {faCalendar} from "@fortawesome/free-regular-svg-icons";
import React from "react";
import TimeByTimeGraph from "../../graphs/TimeByTimeGraph";
import ServerCalendar from "../../calendar/ServerCalendar";
const DayByDayTab = () => {
const {identifier} = useParams();
const {data, loadingError} = useDataRequest(fetchDayByDayGraph, [identifier])
if (loadingError) return <ErrorViewBody error={loadingError}/>
if (!data) return <></>;
return <TimeByTimeGraph data={data}/>
}
const HourByHourTab = () => {
const {identifier} = useParams();
const {data, loadingError} = useDataRequest(fetchHourByHourGraph, [identifier])
if (loadingError) return <ErrorViewBody error={loadingError}/>
if (!data) return <></>;
return <TimeByTimeGraph data={data}/>
}
const ServerCalendarTab = () => {
const {identifier} = useParams();
const {data, loadingError} = useDataRequest(fetchServerCalendarGraph, [identifier])
if (loadingError) return <ErrorViewBody error={loadingError}/>
if (!data) return <></>;
return <ServerCalendar series={data.data} firstDay={data.firstDay}/>
}
const PunchCardTab = () => {
const {identifier} = useParams();
const {data, loadingError} = useDataRequest(fetchPunchCardGraph, [identifier])
if (loadingError) return <ErrorViewBody error={loadingError}/>
if (!data) return <></>;
return <PunchCard series={data.punchCard}/>
}
const OnlineActivityGraphsCard = () => {
const {t} = useTranslation();
return <Card>
<CardTabs tabs={[
{
name: t('html.label.dayByDay'), icon: faChartArea, color: 'blue', href: 'day-by-day',
element: <DayByDayTab/>
}, {
name: t('html.label.hourByHour'), icon: faChartArea, color: 'blue', href: 'hour-by-hour',
element: <HourByHourTab/>
}, {
name: t('html.label.serverCalendar'), icon: faCalendar, color: 'teal', href: 'server-calendar',
element: <ServerCalendarTab/>
}, {
name: t('html.label.punchcard30days'), icon: faBraille, color: 'black', href: 'punchcard',
element: <PunchCardTab/>
},
]}/>
</Card>
}
export default OnlineActivityGraphsCard;

View File

@ -0,0 +1,42 @@
import {useTheme} from "../../hooks/themeHook";
import React, {useEffect} from "react";
import {linegraphButtons} from "../../util/graphs";
import Highcharts from "highcharts/highstock";
import NoDataDisplay from "highcharts/modules/no-data-to-display"
import {useTranslation} from "react-i18next";
const LineGraph = ({id, series}) => {
const {t} = useTranslation()
const {graphTheming} = useTheme();
useEffect(() => {
NoDataDisplay(Highcharts);
Highcharts.setOptions({lang: {noData: t('html.labels.noDataToDisplay')}})
Highcharts.setOptions(graphTheming);
Highcharts.stockChart(id, {
rangeSelector: {
selected: 2,
buttons: linegraphButtons
},
yAxis: {
softMax: 2,
softMin: 0
},
title: {text: ''},
plotOptions: {
areaspline: {
fillOpacity: 0.4
}
},
series: series
})
}, [series, graphTheming])
return (
<div className="chart-area" id={id}>
<span className="loader"/>
</div>
)
}
export default LineGraph

View File

@ -1,12 +1,11 @@
import React, {useEffect} from "react";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {useTheme} from "../../hooks/themeHook";
import Highcharts from "highcharts/highstock";
import {linegraphButtons, tooltip} from "../../util/graphs";
import {tooltip} from "../../util/graphs";
import LineGraph from "./LineGraph";
const PingGraph = ({data}) => {
const {t} = useTranslation();
const {graphTheming} = useTheme();
const [series, setSeries] = useState([]);
useEffect(() => {
const avgPingSeries = {
@ -30,25 +29,11 @@ const PingGraph = ({data}) => {
data: data.min_ping_series,
color: data.colors.min
}
Highcharts.setOptions(graphTheming);
Highcharts.stockChart("ping-graph", {
rangeSelector: {
selected: 2,
buttons: linegraphButtons
},
yAxis: {
softMax: 2,
softMin: 0
},
title: {text: ''},
series: [avgPingSeries, maxPingSeries, minPingSeries]
})
}, [data, graphTheming, t])
setSeries([avgPingSeries, maxPingSeries, minPingSeries]);
}, [data, t])
return (
<div className="chart-area" id="ping-graph">
<span className="loader"/>
</div>
<LineGraph id="ping-graph" series={series}/>
)
}

View File

@ -1,12 +1,11 @@
import React, {useEffect} from "react";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {useTheme} from "../../hooks/themeHook";
import Highcharts from "highcharts/highstock";
import {linegraphButtons, tooltip} from "../../util/graphs";
import {tooltip} from "../../util/graphs";
import LineGraph from "./LineGraph";
const PlayersOnlineGraph = ({data}) => {
const {t} = useTranslation();
const {graphTheming} = useTheme();
const [series, setSeries] = useState([]);
useEffect(() => {
const playersOnlineSeries = {
@ -17,30 +16,11 @@ const PlayersOnlineGraph = ({data}) => {
color: data.color,
yAxis: 0
}
Highcharts.setOptions(graphTheming);
Highcharts.stockChart("online-activity-graph", {
rangeSelector: {
selected: 2,
buttons: linegraphButtons
},
yAxis: {
softMax: 2,
softMin: 0
},
title: {text: ''},
plotOptions: {
areaspline: {
fillOpacity: 0.4
}
},
series: [playersOnlineSeries]
})
}, [data, graphTheming, t])
setSeries([playersOnlineSeries]);
}, [data, t])
return (
<div className="chart-area" id="online-activity-graph">
<span className="loader"/>
</div>
<LineGraph id="players-online-graph" series={series}/>
)
}

View File

@ -0,0 +1,33 @@
import {useTranslation} from "react-i18next";
import React, {useEffect, useState} from "react";
import {tooltip} from "../../util/graphs";
import LineGraph from "./LineGraph";
const TimeByTimeGraph = ({data}) => {
const {t} = useTranslation();
const [series, setSeries] = useState([]);
useEffect(() => {
const uniquePlayers = {
name: t('html.label.uniquePlayers'),
type: 'spline',
tooltip: tooltip.zeroDecimals,
data: data.uniquePlayers,
color: data.colors.playersOnline
};
const newPlayers = {
name: t('html.label.newPlayers'),
type: 'spline',
tooltip: tooltip.zeroDecimals,
data: data.newPlayers,
color: data.colors.newPlayers
};
setSeries([uniquePlayers, newPlayers]);
}, [data, t])
return (
<LineGraph id="day-by-day-graph" series={series}/>
)
}
export default TimeByTimeGraph

View File

@ -172,7 +172,7 @@ const SidebarCollapse = ({item, open, setOpen}) => {
)
}
const renderItem = (item, i, openCollapse, setOpenCollapse) => {
const renderItem = (item, i, openCollapse, setOpenCollapse, t) => {
if (item.contents) {
return <SidebarCollapse key={i}
item={item}
@ -191,7 +191,7 @@ const renderItem = (item, i, openCollapse, setOpenCollapse) => {
}
if (item.name) {
return <div key={i} className="sidebar-heading">{item.name}</div>
return <div key={i} className="sidebar-heading">{t(item.name)}</div>
}
return <hr key={i} className="sidebar-divider"/>
@ -227,7 +227,7 @@ const Sidebar = ({items, showBackButton}) => {
<Item active={false} href="/" icon={faArrowLeft} name={t('html.label.toMainPage')}/>
<Divider/>
</> : ''}
{items.map((item, i) => renderItem(item, i, openCollapse, toggleCollapse))}
{items.map((item, i) => renderItem(item, i, openCollapse, toggleCollapse, t))}
<Divider/>
<FooterButtons/>
</ul>}

View File

@ -1,19 +1,22 @@
import {useEffect, useState} from "react";
import {useNavigation} from "./navigationHook";
export const useDataRequest = (fetchMethod, parameters) => {
const [data, setData] = useState(undefined);
const [loadingError, setLoadingError] = useState(undefined);
const {updateRequested, finishUpdate} = useNavigation();
/*eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
fetchMethod(...parameters).then(({data: json, error}) => {
fetchMethod(...parameters, updateRequested).then(({data: json, error}) => {
if (json) {
setData(json)
setData(json);
finishUpdate(json.timestamp, json.timestamp_f);
} else if (error) {
setLoadingError(error);
}
});
}, [fetchMethod, ...parameters])
}, [fetchMethod, ...parameters, updateRequested])
/* eslint-enable react-hooks/exhaustive-deps */
return {data, loadingError};

View File

@ -18,6 +18,7 @@ export const NavigationContextProvider = ({children}) => {
}, [updating, setUpdateRequested, setUpdating]);
const finishUpdate = useCallback((date, formatted) => {
// TODO Logic to retry if received data is too old
setLastUpdate({date, formatted});
setUpdating(false);
}, [setLastUpdate, setUpdating]);

View File

@ -14,13 +14,13 @@ export const fetchPlayersOnlineGraph = async (identifier) => {
export const fetchDayByDayGraph = async (identifier) => {
const timestamp = Date.now();
const url = `/v1/graph?type=dayByDay&server=${identifier}&timestamp=${timestamp}`;
const url = `/v1/graph?type=uniqueAndNew&server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}
export const fetchHourByHourGraph = async (identifier) => {
const timestamp = Date.now();
const url = `/v1/graph?type=hourByHour&server=${identifier}&timestamp=${timestamp}`;
const url = `/v1/graph?type=hourlyUniqueAndNew&server=${identifier}&timestamp=${timestamp}`;
return doGetRequest(url);
}

View File

@ -1,65 +1,13 @@
import {useParams} from "react-router-dom";
import React from "react";
import {fetchPunchCardGraph} from "../service/serverService";
import {Card, Col, Row} from "react-bootstrap-v5";
import CardTabs from "../components/CardTabs";
import {useTranslation} from "react-i18next";
import {faBraille, faChartArea} from "@fortawesome/free-solid-svg-icons";
import PunchCard from "../components/graphs/PunchCard";
import {faCalendar} from "@fortawesome/free-regular-svg-icons";
import {useDataRequest} from "../hooks/dataFetchHook";
import {ErrorViewBody} from "./ErrorView";
const DayByDayGraph = () => {
return <></>
}
const HourByHourGraph = () => {
return <></>
}
const ServerCalendar = () => {
return <></>
}
const ServerPunchCard = () => {
const {identifier} = useParams();
const {data, loadingError} = useDataRequest(fetchPunchCardGraph, [identifier])
if (loadingError) return <ErrorViewBody error={loadingError}/>
if (!data) return <></>;
return <PunchCard series={data.punchCard}/>
}
const GraphsTabbedCard = () => {
const {t} = useTranslation();
return <Card><CardTabs
tabs={[
{
name: t('html.label.dayByDay'), icon: faChartArea, color: 'blue', href: 'day-by-day',
element: <DayByDayGraph/>
}, {
name: t('html.label.hourByHour'), icon: faChartArea, color: 'blue', href: 'hour-by-hour',
element: <HourByHourGraph/>
}, {
name: t('html.label.serverCalendar'), icon: faCalendar, color: 'teal', href: 'server-calendar',
element: <ServerCalendar/>
}, {
name: t('html.label.punchcard30days'), icon: faBraille, color: 'black', href: 'punchcard',
element: <ServerPunchCard/>
},
]}
/></Card>
}
import {Col, Row} from "react-bootstrap-v5";
import OnlineActivityGraphsCard from "../components/cards/server/OnlineActivityGraphsCard";
const ServerOnlineActivity = () => {
return (
<section className="server_online_activity_overview">
<Row>
<Col lg={12}>
<GraphsTabbedCard/>
<OnlineActivityGraphsCard/>
</Col>
</Row>
<Row>