Skip to content

Commit

Permalink
Merge pull request #12 from acm-cmu/meng/misc
Browse files Browse the repository at this point in the history
Admin Console Changes + Scrimmage Table Timestamps
  • Loading branch information
Meng87 authored Jan 28, 2024
2 parents 4184665 + 88a6272 commit d8d3fce
Show file tree
Hide file tree
Showing 10 changed files with 855 additions and 30 deletions.
555 changes: 548 additions & 7 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@mui/icons-material": "^5.15.4",
"@mui/material": "^5.15.4",
"@mui/x-date-pickers": "^6.19.0",
"@mui/x-date-pickers": "^6.19.2",
"@next-auth/dynamodb-adapter": "^0.3.1-canary.207",
"@popperjs/core": "^2.11.6",
"@reduxjs/toolkit": "^1.8.5",
Expand All @@ -33,6 +33,7 @@
"classnames": "^2.3.2",
"cookie-cutter": "^0.2.0",
"cookies-next": "^4.1.0",
"dayjs": "^1.11.10",
"material-react-table": "^2.6.1",
"next": "13.0.1",
"next-auth": "^4.16.4",
Expand All @@ -48,6 +49,7 @@
"react-resize-detector": "^7.1.2",
"react-select": "^5.7.0",
"react-toastify": "^9.1.1",
"recharts": "^2.11.0",
"swr": "^1.3.0",
"uuid": "^9.0.0"
},
Expand Down
71 changes: 71 additions & 0 deletions src/components/BotTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useMemo } from 'react';
import {
MaterialReactTable,
useMaterialReactTable,
type MRT_ColumnDef,
} from 'material-react-table';
import { TeamBot } from '@pages/api/admin/admin-bot-history';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';

const DownloadCell = (cell: { row: { original: { bot: any } } }) => {
const { row } = cell;
const url = row.original.bot;
if (url === 'unknown') {
return <div>N/A</div>;
}
return <a href={url}>Download</a>;
};

const BotTable = (props: { data: TeamBot[] }) => {
const { data } = props;
const columns = useMemo<MRT_ColumnDef<TeamBot>[]>(
() => [
{
accessorKey: 'team',
header: 'Team',
filterVariant: 'autocomplete',
size: 400,
},
{
accessorFn: (originalRow) => new Date(originalRow.upload_time), // convert to date for sorting and filtering
id: 'upload_time',
header: 'Upload Time',
filterVariant: 'datetime-range',
Cell: ({ cell }) => cell.getValue<Date>().toLocaleString('en-US'), // convert back to string for display
size: 500,
},
{
accessorFn: (originalRow) => originalRow.upload_name,
header: 'Bot',
Cell: DownloadCell,
filterVariant: 'select',
grow: true,
size: 180,
},
],
[],
);

const table = useMaterialReactTable({
columns,
data: data == null ? [] : data,
enableFacetedValues: true,
enableColumnResizing: true,
enableFullScreenToggle: false,
initialState: { showColumnFilters: true },
});

return <MaterialReactTable table={table} />;
};

const BotTableWithLocalizationProvider = (props: { data: TeamBot[] }) => {
const { data } = props;
return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<BotTable data={data} />
</LocalizationProvider>
);
};

export default BotTableWithLocalizationProvider;
43 changes: 31 additions & 12 deletions src/components/MatchTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
type MRT_ColumnDef,
} from 'material-react-table';
import { Match } from '@pages/api/admin/admin-match-history';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';

const ReplayCell = ({ cell }: { cell: any }) => (
<a href={cell.getValue()}>Download</a>
Expand All @@ -18,7 +20,7 @@ const MatchTable = (props: { data: Match[] }) => {
accessorKey: 'id',
header: 'ID',
filterVariant: 'autocomplete',
size: 80,
size: 130,
},
{
accessorKey: 'player1',
Expand All @@ -34,6 +36,12 @@ const MatchTable = (props: { data: Match[] }) => {
size: 200,
maxSize: 140,
},
{
accessorKey: 'outcome',
header: 'Winner',
filterVariant: 'select',
size: 140,
},
{
accessorKey: 'category',
header: 'Category',
Expand All @@ -47,23 +55,25 @@ const MatchTable = (props: { data: Match[] }) => {
size: 150,
},
{
accessorKey: 'status',
header: 'Status',
accessorKey: 'replay',
header: 'Replay',
Cell: ReplayCell,
filterVariant: 'select',
size: 130,
},
{
accessorKey: 'outcome',
header: 'Winner',
accessorKey: 'status',
header: 'Status',
filterVariant: 'select',
size: 140,
size: 130,
},
{
accessorKey: 'replay',
header: 'Replay',
Cell: ReplayCell,
filterVariant: 'select',
size: 130,
accessorFn: (originalRow) => new Date(originalRow.timestamp), // convert to date for sorting and filtering
id: 'timestamp',
header: 'Timestamp',
filterVariant: 'datetime-range',
Cell: ({ cell }) => cell.getValue<Date>().toLocaleString('en-US'), // convert back to string for display
size: 500,
},
],
[],
Expand All @@ -81,4 +91,13 @@ const MatchTable = (props: { data: Match[] }) => {
return <MaterialReactTable table={table} />;
};

export default MatchTable;
const MatchTableWithLocalizationProvider = (props: { data: Match[] }) => {
const { data } = props;
return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<MatchTable data={data} />
</LocalizationProvider>
);
};

export default MatchTableWithLocalizationProvider;
120 changes: 113 additions & 7 deletions src/pages/admin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,50 @@ import { UserLayout } from '@layout';
import { NextPage } from 'next';
import { useSession } from 'next-auth/react';
import Router from 'next/router';
import { useEffect } from 'react';
import { FunctionComponent, useEffect, useMemo } from 'react';
import axios, { AxiosError, AxiosResponse } from 'axios';
import { Button, Card } from 'react-bootstrap';
import { toast } from 'react-toastify';
import useSWR from 'swr';
import MatchTable from '@components/MatchTable';
import BotTable from '@components/BotTable';
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
} from 'recharts';
import { Match } from '@pages/api/admin/admin-match-history';

const CustomizedAxisTick: FunctionComponent<any> = (props: any) => {
const { x, y, payload } = props;

return (
<g transform={`translate(${x},${y})`}>
<text x={0} y={0} dy={16} textAnchor='end' transform='rotate(-20)'>
{payload.value}
</text>
</g>
);
};

const Admin: NextPage = () => {
const { status, data } = useSession();

const fetcher = async (url: string) => axios.get(url).then((res) => res.data);

const { data: MatchData, mutate } = useSWR(
const { data: MatchData, mutate: mutateMatch } = useSWR(
'/api/admin/admin-match-history',
fetcher,
);

const { data: BotData, mutate: mutateBot } = useSWR(
'/api/admin/admin-bot-history',
fetcher,
);

useEffect(() => {
if (status === 'unauthenticated') Router.replace('/auth/login');
}, [status]);
Expand Down Expand Up @@ -198,6 +225,37 @@ const Admin: NextPage = () => {
modifyCodeSubmissions(false);
};

const aggregateMatchesByMinute = (matches: Match[]) => {
if (!matches) {
return [];
}
const aggregatedData: { [id: string]: number } = {};
matches.forEach((item: Match) => {
const minute = new Date(item.timestamp).toLocaleString('en-US', {
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
}) as keyof object; // Extract YYYY-MM-DDTHH:MM
if (aggregatedData[minute]) {
aggregatedData[minute] += 1;
} else {
aggregatedData[minute] = 1;
}
});
// Convert aggregated data to array format required by Recharts
const chartData = Object.keys(aggregatedData).map((time) => ({
time,
matches: aggregatedData[time],
}));
return chartData;
};

const aggregatedData = useMemo(
() => aggregateMatchesByMinute(MatchData),
[MatchData],
);

if (status === 'authenticated') {
if (data?.user?.role === 'user') {
Router.replace('/unauthorized');
Expand All @@ -222,7 +280,7 @@ const Admin: NextPage = () => {
<Button
onClick={enableBracketSwitching}
variant='dark'
className='mr-3'
className='me-3'
>
Enable Bracket Switching
</Button>
Expand All @@ -235,7 +293,7 @@ const Admin: NextPage = () => {
<Button
onClick={enableTeamModifications}
variant='dark'
className='mr-3'
className='me-3'
>
Enable Team Modifications
</Button>
Expand All @@ -248,7 +306,7 @@ const Admin: NextPage = () => {
<Button
onClick={enableScrimmageRequests}
variant='dark'
className='mr-3'
className='me-3'
>
Enable Scrimmage Requests
</Button>
Expand All @@ -265,7 +323,7 @@ const Admin: NextPage = () => {
<Button
onClick={enableCodeSubmissions}
variant='dark'
className='mr-3'
className='me-3'
>
Enable Code Submissions
</Button>
Expand Down Expand Up @@ -317,14 +375,62 @@ const Admin: NextPage = () => {
variant='dark'
className='mb-3'
onClick={async () => {
mutate();
mutateMatch();
}}
>
Refresh
</Button>
<MatchTable data={MatchData} />
</Card.Body>
</Card>
<br />
<Card>
<Card.Body>
<Card.Title>Global Bot History</Card.Title>
<Button
variant='dark'
className='mb-3'
onClick={async () => {
mutateBot();
}}
>
Refresh
</Button>
<BotTable data={BotData} />
</Card.Body>
</Card>
<br />
<Card>
<Card.Body>
<Card.Title>Statistics</Card.Title>
<Button
variant='dark'
className='mb-3'
onClick={async () => {
mutateMatch();
}}
>
Refresh
</Button>
<div>
<Card.Subtitle className='mb-3'>
Number of Matches Over Time (Aggregated by Minute)
</Card.Subtitle>
<LineChart
width={1100}
height={300}
data={aggregatedData}
margin={{ top: 5, right: 30, left: 10, bottom: 50 }}
>
<XAxis dataKey='time' tick={<CustomizedAxisTick />} />
<YAxis />
<CartesianGrid strokeDasharray='3 3' />
<Tooltip />
<Line type='monotone' dataKey='matches' stroke='#8884d8' />
</LineChart>
</div>
</Card.Body>
</Card>
</UserLayout>
);
}
Expand Down
Loading

0 comments on commit d8d3fce

Please sign in to comment.