Skip to content

Commit

Permalink
Merge pull request online-go#2438 from GreenAsJade/corner_case_protec…
Browse files Browse the repository at this point in the history
…tion_for_community_moderation

Corner case handling for community moderator actions
  • Loading branch information
anoek authored Dec 2, 2023
2 parents 1f3cdd4 + 688549b commit b6f33aa
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export function IncidentReportTracker(): JSX.Element {
navigate(`/reports-center/all/${report_id}`);
};

const reports = report_manager.sorted_active_incident_reports;
const reports = report_manager.getAvailableReports();
const hide_indicator = (reports.length === 0 && !user.is_moderator) || prefer_hidden;

function getReportType(report: Report): string {
Expand Down
18 changes: 17 additions & 1 deletion src/lib/report_manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import { MOD_POWER_HANDLE_SCORE_CHEAT, MOD_POWER_HANDLE_ESCAPING } from "./misc"

export const DAILY_REPORT_GOAL = 10;

const DONT_OFFER_COMMUNITY_MODERATION_TYPES_TO_MODERATORS = false;

interface Vote {
voter_id: number;
action: string;
Expand Down Expand Up @@ -229,13 +231,27 @@ class ReportManager extends EventEmitter<Events> {
// don't hand community moderation reports to full mods unless the report is escalated,
// because community moderators are supposed to do these!
if (
DONT_OFFER_COMMUNITY_MODERATION_TYPES_TO_MODERATORS &&
user.is_moderator &&
!(report.moderator?.id === user.id) && // maybe they already have it, so they need to see it
(report.report_type === "score_cheating" || report.report_type === "escaping") &&
!report.escalated
) {
return false;
}
return !report.moderator || report.moderator?.id === user.id;

// Never give a claimed report to community moderators
if (!user.is_moderator && report.moderator?.id) {
console.log("Not giving to community moderator", report.id);
return false;
}

if (report.moderator && report.moderator.id !== user.id) {
// claimed reports are not given out to others
return false;
}

return true;
});
}

Expand Down
62 changes: 35 additions & 27 deletions src/views/ReportsCenter/ModerationActionSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@
*/

import * as React from "react";
import { Report } from "report_manager";
import { pgettext } from "translate";
import { _, pgettext } from "translate";

import * as DynamicHelp from "react-dynamic-help";

interface ModerationActionSelectorProps {
report: Report;
available_actions: string[];
enable: boolean;
claim: () => void;
submit: (action: string) => void;
Expand Down Expand Up @@ -60,7 +59,7 @@ const ACTION_PROMPTS = {
};

export function ModerationActionSelector({
report,
available_actions,
enable,
claim,
submit,
Expand All @@ -76,37 +75,46 @@ export function ModerationActionSelector({
const { ref: voting_pane } = registerTargetItem("voting-pane");
const { ref: escalate_option } = registerTargetItem("escalate-option");

const action_choices = available_actions ? available_actions : ["escalate"];

return (
<div className="voting" ref={voting_pane}>
<div className="voting-pane" ref={voting_pane}>
<h4>
{pgettext(
"The heading for community moderators 'action choices' section",
"Actions",
)}
</h4>
{report.available_actions.map((a) => (
<div
key={a}
className="action-selector"
ref={a === "escalate" ? escalate_option : null}
>
<input
id={a}
name="availableActions"
type="radio"
checked={selectedOption === a}
value={a}
onChange={updateSelectedAction}
/>
<label htmlFor={a}>{ACTION_PROMPTS[a]}</label>
{(!available_actions || null) && (
<div className="no-report-actions-note">
{_("This report has no available actions yet. You can escalate or ignore it.")}
</div>
)}
{!enable && (
<div className="diabled-actions-note">
{_("This report was handled after you decided to look at it!")}
</div>
))}
{(report.available_actions || null) && (
<button
className="success"
disabled={!enable}
onClick={() => submit(selectedOption)}
>
)}
{enable &&
action_choices.map((a) => (
<div
key={a}
className="action-selector"
ref={a === "escalate" ? escalate_option : null}
>
<input
id={a}
name="availableActions"
type="radio"
checked={selectedOption === a}
value={a}
onChange={updateSelectedAction}
/>
<label htmlFor={a}>{ACTION_PROMPTS[a]}</label>
</div>
))}
{((action_choices && enable) || null) && (
<button className="success" onClick={() => submit(selectedOption)}>
{pgettext("A label on a button for submitting a vote", "Vote")}
</button>
)}
Expand Down
9 changes: 9 additions & 0 deletions src/views/ReportsCenter/ReportsCenter.styl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ reports_center_content_width=56rem
}
}


.voting-pane {
padding-bottom: 0.5rem; // intended to create margin for rdh highlighter

.no-report-actions-note, .disabled-actions-note {
padding: 0 1rem 1rem 1rem;
}
}

.actions {
display: grid;
grid-template-columns: 1fr 1fr;
Expand Down
9 changes: 7 additions & 2 deletions src/views/ReportsCenter/ViewReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,15 @@ export function ViewReport({ report_id, reports, onChange }: ViewReportProps): J
const [reportState, setReportState] = React.useState(report?.state);
const [isAnnulQueueModalOpen, setIsAnnulQueueModalOpen] = React.useState(false);
const [annulQueue, setAnnulQueue] = React.useState<null | any[]>(report?.detected_ai_games);
const [availableActions, setAvailableActions] = React.useState<string[]>(null);

const related = report_manager.getRelatedReports(report_id);

React.useEffect(() => {
if (report_id) {
// For some reason we have to capture the state of the report at the time that report_id goes valid
// It's not clear why, but there are subsequent renders where the report state goes away, so ...
// capture what you want to use here! ...
report_manager
.getReport(report_id)
.then((report) => {
Expand All @@ -72,6 +76,7 @@ export function ViewReport({ report_id, reports, onChange }: ViewReportProps): J
setModeratorId(report?.moderator?.id);
setReportState(report?.state);
setAnnulQueue(report?.detected_ai_games);
setAvailableActions(report?.available_actions);
})
.catch((err) => {
console.error(err);
Expand Down Expand Up @@ -448,9 +453,9 @@ export function ViewReport({ report_id, reports, onChange }: ViewReportProps): J
{((!user.is_moderator && user.moderator_powers) || null) && (
<div className="voting">
<ModerationActionSelector
report={report}
available_actions={availableActions}
claim={() => {
/* dont claim*/
/* community moderators don't claim reports */
}}
submit={(action) => {
void report_manager.vote(report.id, action);
Expand Down

0 comments on commit b6f33aa

Please sign in to comment.