Skip to content

Commit

Permalink
Merge branch 'devel' of github.com:online-go/online-go.com into devel
Browse files Browse the repository at this point in the history
  • Loading branch information
anoek committed Nov 22, 2024
2 parents dea5cc0 + 68be95f commit 1d6bc05
Showing 1 changed file with 227 additions and 5 deletions.
232 changes: 227 additions & 5 deletions src/views/ReportsCenter/ReportsCenterCMDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,21 @@ interface ReportCount {
non_consensus: number;
}

interface ReportAlignmentCount {
date: string;
escalated: number;
unanimous: number;
non_unanimous: number;
}

interface CMVotingOutcomeData {
[reportType: string]: ReportCount[];
}

interface CMGroupVotingAlignmentData {
[reportType: string]: ReportAlignmentCount[];
}

interface IndividualCMVotingOutcomeData {
user_id: number;
vote_data: CMVotingOutcomeData;
Expand All @@ -62,13 +73,14 @@ function startOfWeek(the_date: Date): Date {
// TBD: it might be nice if this number was dynamically provided by the server, but
// we are already possibly hitting it hard for these rollups

const EXPECTED_MAX_WEEKLY_CM_REPORTS = 160;
const Y_STEP_SIZE = 40; // must divide evenly into EXPECTED_MAX_WEEKLY_CM_REPORTS
const EXPECTED_MAX_WEEKLY_CM_REPORTS = 200;
const Y_STEP_SIZE = 40; // must divide nicely into EXPECTED_MAX_WEEKLY_CM_REPORTS

interface CMVoteCountGraphProps {
vote_data: ReportCount[];
period: number;
}

const CMVoteCountGraph = ({ vote_data, period }: CMVoteCountGraphProps): JSX.Element => {
if (!vote_data) {
vote_data = [];
Expand Down Expand Up @@ -283,10 +295,220 @@ const CMVoteCountGraph = ({ vote_data, period }: CMVoteCountGraphProps): JSX.Ele
);
};

interface CMVotingGroupGraphProps {
vote_data: ReportAlignmentCount[];
period: number;
}

const CMVotingGroupGraph = ({ vote_data, period }: CMVotingGroupGraphProps): JSX.Element => {
if (!vote_data) {
vote_data = [];
}

const aggregateDataByWeek = React.useMemo(() => {
const aggregated: {
[key: string]: {
escalated: number;
unanimous: number;
non_unanimous: number;
total: number;
};
} = {};

vote_data.forEach(({ date, escalated, unanimous, non_unanimous }) => {
const weekStart = startOfWeek(new Date(date)).toISOString().slice(0, 10); // Get week start and convert to ISO string for key

if (!aggregated[weekStart]) {
aggregated[weekStart] = { escalated: 0, unanimous: 0, non_unanimous: 0, total: 0 };
}
aggregated[weekStart].escalated += escalated;
aggregated[weekStart].unanimous += unanimous;
aggregated[weekStart].non_unanimous += non_unanimous;
aggregated[weekStart].total += unanimous + non_unanimous;
});

return Object.entries(aggregated).map(([date, counts]) => ({
date,
...counts,
}));
}, [vote_data]);

const totals_data = React.useMemo(() => {
return [
{
id: "unanimous",
data: dropCurrentPeriod(
aggregateDataByWeek.map((week) => ({
x: week.date,
y: week.unanimous,
})),
),
},
{
id: "escalated",
data: dropCurrentPeriod(
aggregateDataByWeek.map((week) => ({
x: week.date,
y: week.escalated,
})),
),
},
{
id: "non-unanimous",
data: dropCurrentPeriod(
aggregateDataByWeek.map((week) => ({
x: week.date,
y: week.non_unanimous,
})),
),
},
];
}, [aggregateDataByWeek]);

const percent_data = React.useMemo(
() => [
{
id: "unanimous",
data: aggregateDataByWeek.map((week) => ({
x: week.date,
y: week.unanimous / week.total,
})),
},
{
id: "escalated",
data: aggregateDataByWeek.map((week) => ({
x: week.date,
y: week.escalated / week.total,
})),
},
{
id: "non-unanimous",
data: aggregateDataByWeek.map((week) => ({
x: week.date,
y: week.non_unanimous / week.total,
})),
},
],
[aggregateDataByWeek],
);

const chart_theme =
data.get("theme") === "light" // (Accessible theme TBD - this assumes accessible is dark for now)
? {
/* nivo defaults work well with our light theme */
}
: {
text: { fill: "#FFFFFF" },
tooltip: { container: { color: "#111111" } },
grid: { line: { stroke: "#444444" } },
};

const line_colors = {
unanimous: "rgba(0, 128, 0, 1)", // green
escalated: "rgba(255, 165, 0, 1)", // orange
"non-unanimous": "rgba(255, 0, 0, 1)", // red
};

if (!totals_data[0].data.length) {
return <div className="aggregate-vote-activity-graph">No activity yet</div>;
}

return (
<div className="aggregate-vote-activity-graph">
<div className="totals-graph">
<ResponsiveLine
data={totals_data}
colors={({ id }) => line_colors[id as keyof typeof line_colors]}
animate
curve="monotoneX"
enablePoints={false}
enableSlices="x"
axisBottom={{
format: "%d %b %g",
tickValues: "every week",
}}
xFormat="time:%Y-%m-%d"
xScale={{
type: "time",
min: format(
startOfWeekDateFns(subDays(new Date(), period), { weekStartsOn: 1 }),
"yyyy-MM-dd",
),
format: "%Y-%m-%d",
useUTC: false,
precision: "day",
}}
axisLeft={{
tickValues: Array.from(
{ length: EXPECTED_MAX_WEEKLY_CM_REPORTS / Y_STEP_SIZE + 1 },
(_, i) => i * Y_STEP_SIZE,
),
}}
yScale={{
stacked: false,
type: "linear",
min: 0,
max: EXPECTED_MAX_WEEKLY_CM_REPORTS,
}}
margin={{
bottom: 40,
left: 60,
right: 40,
top: 5,
}}
theme={chart_theme}
/>
</div>
<div className="totals-graph">
<ResponsiveLine
data={percent_data}
colors={({ id }) => line_colors[id as keyof typeof line_colors]}
animate
curve="monotoneX"
enablePoints={false}
enableSlices="x"
axisBottom={{
format: "%d %b %g",
tickValues: "every week",
}}
xFormat="time:%Y-%m-%d"
xScale={{
type: "time",
min: format(
startOfWeekDateFns(subDays(new Date(), period), { weekStartsOn: 1 }),
"yyyy-MM-dd",
),
format: "%Y-%m-%d",
useUTC: false,
precision: "day",
}}
axisLeft={{
format: (d) => `${Math.round(d * 100)}%`, // Format ticks as percentages
tickValues: 6,
}}
yFormat=" >-.0p"
yScale={{
stacked: false,
type: "linear",
max: 1,
}}
margin={{
bottom: 40,
left: 60,
right: 40,
top: 5,
}}
theme={chart_theme}
/>
</div>
</div>
);
};

export function ReportsCenterCMDashboard(): JSX.Element {
const user = useUser();
const [selectedTabIndex, setSelectedTabIndex] = React.useState(user.moderator_powers ? 0 : 1);
const [vote_data, setVoteData] = React.useState<CMVotingOutcomeData | null>(null);
const [vote_data, setVoteData] = React.useState<CMGroupVotingAlignmentData | null>(null);
const [users_data, setUsersData] = React.useState<CMVotingOutcomeData | null>(null);

// `Tabs` isn't expecting the possibility that the initial tab is not zero.
Expand All @@ -308,7 +530,7 @@ export function ReportsCenterCMDashboard(): JSX.Element {
const fetchVoteData = () => {
get(`moderation/cm_voting_outcomes`)
.then((response) => {
const fetchedData: CMVotingOutcomeData = response;
const fetchedData: CMGroupVotingAlignmentData = response;
setVoteData(fetchedData);
})
.catch((err) => {
Expand Down Expand Up @@ -361,7 +583,7 @@ export function ReportsCenterCMDashboard(): JSX.Element {
<div key={report_type}>
<h3>{report_type}</h3>
{vote_data[report_type] ? (
<CMVoteCountGraph
<CMVotingGroupGraph
vote_data={vote_data[report_type]}
period={120}
/>
Expand Down

0 comments on commit 1d6bc05

Please sign in to comment.