From 04f3f28b91c249386c38058cde45ba26effad814 Mon Sep 17 00:00:00 2001 From: Petr Vecera Date: Mon, 6 Jun 2022 22:57:31 +0200 Subject: [PATCH] Add total games stats and error handling (#224) --- packages/web/src/App.tsx | 109 +++++++++--------- packages/web/src/coh/types.ts | 1 + .../web/src/components/error-boundary.tsx | 66 +++++++++++ packages/web/src/components/main-footer.tsx | 3 +- packages/web/src/config.tsx | 3 + packages/web/src/pages/about/about.tsx | 19 ++- .../src/pages/map-stats/map-stats-details.tsx | 24 +++- .../src/pages/stats/custom-stats-details.tsx | 2 +- .../stats/general-charts/total-games-pie.tsx | 69 +++++++++++ .../web/src/pages/stats/general-stats.tsx | 19 +-- packages/web/src/pages/stats/stats-header.tsx | 37 +++++- 11 files changed, 268 insertions(+), 84 deletions(-) create mode 100644 packages/web/src/components/error-boundary.tsx create mode 100644 packages/web/src/pages/stats/general-charts/total-games-pie.tsx diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index cdb7af73..014067f3 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -21,6 +21,7 @@ import Leaderboards from "./pages/ladders"; import PlayerCard from "./pages/players"; import MapStats from "./pages/map-stats"; import LiveMatches from "./pages/live-matches"; +import { ErrorBoundary } from "./components/error-boundary"; const { Content } = Layout; @@ -43,59 +44,61 @@ const App: React.FC = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/src/coh/types.ts b/packages/web/src/coh/types.ts index 9610905d..71002de6 100644 --- a/packages/web/src/coh/types.ts +++ b/packages/web/src/coh/types.ts @@ -93,6 +93,7 @@ type TypeAnalysisObject = { usf: Record; }; factionMatrix: Record; + totalGames?: number; }; interface StatsDataObject { diff --git a/packages/web/src/components/error-boundary.tsx b/packages/web/src/components/error-boundary.tsx new file mode 100644 index 00000000..e276360b --- /dev/null +++ b/packages/web/src/components/error-boundary.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import { AlertBox } from "./alert-box"; +import { Row, Typography } from "antd"; +import config from "../config"; +const { Text } = Typography; + +class ErrorBoundary extends React.Component { + constructor(props: any) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: any) { + // Update state so the next render will show the fallback UI. + return { hasError: true }; + } + + componentDidCatch(error: any, errorInfo: any) { + // You can also log the error to an error reporting service + // logErrorToMyService(error, errorInfo); + } + + render() { + // @ts-ignore + if (this.state.hasError) { + // You can render any custom fallback UI + return ( + + + Please refresh the app (press F5). If the problem persists after refreshing the + page please report it together with screenshot, url and + preferably copy all the error text which is in the Console of browser developer + tools ( + + {" "} + how to open dev tools + + ) in our Discord{" "} + + {"Discord + + + } + /> + + ); + } + + return this.props.children; + } +} + +export { ErrorBoundary }; diff --git a/packages/web/src/components/main-footer.tsx b/packages/web/src/components/main-footer.tsx index a968e717..7859af23 100644 --- a/packages/web/src/components/main-footer.tsx +++ b/packages/web/src/components/main-footer.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Footer } from "antd/lib/layout/layout"; import { Divider } from "antd"; import { Link } from "react-router-dom"; +import config from "../config"; export const MainFooter: React.FC = () => { return ( @@ -19,7 +20,7 @@ export const MainFooter: React.FC = () => { {"GitHub {" "} - + {"Discord
diff --git a/packages/web/src/config.tsx b/packages/web/src/config.tsx index 3d49701f..60d1ef98 100644 --- a/packages/web/src/config.tsx +++ b/packages/web/src/config.tsx @@ -19,6 +19,8 @@ const firebaseFunctions = { location: "us-east4", }; +const discordInviteLink = "https://discord.gg/jRrnwqMfkr"; + const devHostnames = ["localhost", "coh2-ladders-dev.web.app"]; const rrfConfig: Partial = { @@ -32,6 +34,7 @@ const config = { rrfConfig, firebaseFunctions, devHostnames, + discordInviteLink, }; // The date when we exported the data for the bulletins and commanders diff --git a/packages/web/src/pages/about/about.tsx b/packages/web/src/pages/about/about.tsx index 52c3fb8c..a85b409e 100644 --- a/packages/web/src/pages/about/about.tsx +++ b/packages/web/src/pages/about/about.tsx @@ -55,12 +55,19 @@ const About: React.FC = () => {

- However based on{" "} - - this data - {" "} - from 2017 we expect that there is around ~50k matches/day. Which means{" "} - we are processing around 8% of all games. + + However based on{" "} + + this data + {" "} + from 2017 we expect that there is around ~50k matches/day. Which means{" "} + we are processing around 8% of all games. + +
+ Update June 2022: Thanks to the access to the live games we know that we track 40% of all + games. +
+ To be precise: 1v1 - 25%, 2v2 - 37%, 3v3 - 41%, 4v4 - 47%. We aim to get to 100%

The amount of data with some types of games is really a problem. You can see that winrate diff --git a/packages/web/src/pages/map-stats/map-stats-details.tsx b/packages/web/src/pages/map-stats/map-stats-details.tsx index 61cb80b0..7a7f52c3 100644 --- a/packages/web/src/pages/map-stats/map-stats-details.tsx +++ b/packages/web/src/pages/map-stats/map-stats-details.tsx @@ -71,7 +71,7 @@ const MapStatsDetails: React.FC = ({ urlChanger, specificData }) => { const frequency = query.get("range") || ""; let map = query.get("map") || "8p_redball_express"; - // const totalGames = specificData.totalGames || null; + const totalGames = specificData.totalGames || null; // We added total games to the object with the maps / we need to remove it // We need to do a copy of the object because we can't remove the key otherwise const data: Record = JSON.parse(JSON.stringify(specificData)); @@ -117,6 +117,22 @@ const MapStatsDetails: React.FC = ({ urlChanger, specificData }) => { ); }; + let gamesAnalyzed = <>Games analyzed {amountOfGames}; + + if (totalGames && totalGames > amountOfGames) { + gamesAnalyzed = ( + <> + Games analyzed {amountOfGames}/{totalGames} -{" "} + {Math.round((amountOfGames / totalGames) * 100)}%{" "} + + + ); + } + const cardWidth = 510; const cardHeight = 420; @@ -127,9 +143,7 @@ const MapStatsDetails: React.FC = ({ urlChanger, specificData }) => {
- - Amount of games for this analysis {amountOfGames} - + {gamesAnalyzed}
{ @@ -179,7 +193,7 @@ const MapStatsDetails: React.FC = ({ urlChanger, specificData }) => { = ({ urlChanger, specificData }) => { <> - + diff --git a/packages/web/src/pages/stats/general-charts/total-games-pie.tsx b/packages/web/src/pages/stats/general-charts/total-games-pie.tsx new file mode 100644 index 00000000..63c32fd1 --- /dev/null +++ b/packages/web/src/pages/stats/general-charts/total-games-pie.tsx @@ -0,0 +1,69 @@ +import { ResponsivePie } from "@nivo/pie"; +import React from "react"; +import { Empty } from "antd"; + +import { StatsDataObject, statsTypesInDB } from "../../../coh/types"; + +interface FactionsPlayedPieChartProps { + data: StatsDataObject; +} + +export const TotalGamesPieChart: React.FC = ({ data }) => { + const chartData = []; + + // "1v1", "2v2", "3v3" ... + for (let type of statsTypesInDB) { + chartData.push({ + id: type, + label: type, + value: data[type as "1v1" | "2v2" | "3v3" | "4v4"].totalGames, + }); + } + + if (!data["1v1"].totalGames && !((data["1v1"].totalGames || 0) > data["1v1"].matchCount)) { + return ; + } + + return ( + + ); +}; diff --git a/packages/web/src/pages/stats/general-stats.tsx b/packages/web/src/pages/stats/general-stats.tsx index 8599564c..59bdeb96 100644 --- a/packages/web/src/pages/stats/general-stats.tsx +++ b/packages/web/src/pages/stats/general-stats.tsx @@ -4,12 +4,12 @@ import { useMediaQuery } from "react-responsive"; import { FactionsPlayedPieChart } from "./general-charts/factions-pie"; import { StatsDataObject } from "../../coh/types"; import { TypeOfGamesPieChart } from "./general-charts/types-games-pie"; -import { Helper } from "../../components/helper"; import { FactionsBarStackedChart } from "./general-charts/factions-bar-stacked"; import { TotalFactionWinRateChart } from "./general-charts/winRate-bar"; import { AverageGameTimeBarChart } from "./general-charts/average-gametime-bar"; import { TotalTimePieChart } from "./general-charts/total-time-pie"; import { FactionWinRateStackedChart } from "./general-charts/winRate-bar-stacked"; +import { TotalGamesPieChart } from "./general-charts/total-games-pie"; interface IProps { data: StatsDataObject; @@ -56,19 +56,10 @@ const GeneralStats: React.FC = ({ data }) => { wrap style={{ display: "flex", maxWidth: 1800, justifyContent: "center" }} > - - {`Amount of games analyzed `} - - - } - > + + + + diff --git a/packages/web/src/pages/stats/stats-header.tsx b/packages/web/src/pages/stats/stats-header.tsx index 4a0769df..3b6f69d0 100644 --- a/packages/web/src/pages/stats/stats-header.tsx +++ b/packages/web/src/pages/stats/stats-header.tsx @@ -4,6 +4,7 @@ import { capitalize, useQuery } from "../../utils/helpers"; import { Radio, RadioChangeEvent, Row, Tooltip, Typography } from "antd"; import PatchNotification from "../../components/patch-notifications"; import { StatsDataObject, statTypesInDbAsType } from "../../coh/types"; +import { Helper } from "../../components/helper"; const { Text } = Typography; @@ -16,7 +17,7 @@ interface IProps { const StatsHeader: React.FC = ({ urlChanger, data }) => { const query = useQuery(); - const type = query.get("type") || "4v4"; + const type = (query.get("type") as "1v1" | "2v2" | "3v3" | "4v4" | "general") || "4v4"; const race = query.get("race") || "wermacht"; const sourceIsAll = query.get("statsSource") !== "top200"; @@ -25,16 +26,24 @@ const StatsHeader: React.FC = ({ urlChanger, data }) => { const toTimeStamp = query.get("toTimeStamp") || ""; const frequency = query.get("range") || ""; + let totalGames: null | number = null; let matchCount = 0; if (type && type !== "general") { - // @ts-ignore matchCount = data[type].matchCount; + totalGames = data[type].totalGames || null; } else { matchCount = data["1v1"].matchCount + - data["1v1"].matchCount + + data["2v2"].matchCount + data["3v3"].matchCount + data["4v4"].matchCount; + + totalGames = 0; + totalGames += data["1v1"].totalGames || 0; + totalGames += data["2v2"].totalGames || 0; + totalGames += data["3v3"].totalGames || 0; + totalGames += data["4v4"].totalGames || 0; + totalGames = totalGames === 0 ? null : totalGames; } // Page title @@ -69,6 +78,26 @@ const StatsHeader: React.FC = ({ urlChanger, data }) => { ); }; + const GamesAnalyzed = () => { + let gamesAnalyzed = <>Games analyzed {matchCount}; + + if (totalGames && totalGames > matchCount) { + gamesAnalyzed = ( + <> + Games analyzed {matchCount}/{totalGames} - {Math.round((matchCount / totalGames) * 100)} + %{" "} + + + ); + } + + return gamesAnalyzed; + }; + return ( <> @@ -77,7 +106,7 @@ const StatsHeader: React.FC = ({ urlChanger, data }) => {
- Amount of games for this analysis {`${matchCount}`} +