Skip to content

Commit

Permalink
Merge pull request #2844 from GreenAsJade/cm_vote_to_suspend
Browse files Browse the repository at this point in the history
Support for CMs voting for suspend
  • Loading branch information
anoek authored Oct 5, 2024
2 parents 297724b + 896efce commit 22813ae
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 35 deletions.
18 changes: 17 additions & 1 deletion src/components/AccountWarning/CannedMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,23 @@ moderator to let them know.
Thanks for your recent report about {{bot}}.
We've notified the owner of that bot.
`),
`),
{ bot },
),
ack_suspended: (reported) =>
interpolate(
_(`
Thank you for your report. {{reported}} is a repeat offender, their account has been suspended.
`),
{ reported },
),

ack_suspended_and_annul: (reported) =>
interpolate(
_(`
Thank you for your report. {{reported}} is a repeat offender, their has been suspended. \
The reported game has been annulled.
`),
{ reported },
),
};
13 changes: 13 additions & 0 deletions src/components/ModerateUser/ModerateUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,19 @@ export class ModerateUser extends Modal<Events, ModerateUserProperties, any> {
onRetractOffer={this.retractOffer}
onRemovePower={this.removePower}
/>
<ModerationOfferControl
ability={pgettext(
"Label for a button to let a community moderator vote for suspension",
"Vote for Suspension",
)}
ability_mask={MODERATOR_POWERS.SUSPEND}
currently_offered={this.state.offered_moderator_powers}
moderator_powers={this.state.moderator_powers}
previously_rejected={this.state.mod_powers_rejected}
onMakeOffer={this.makeOffer}
onRetractOffer={this.retractOffer}
onRemovePower={this.removePower}
/>
</div>
)}
<div className="buttons">
Expand Down
2 changes: 2 additions & 0 deletions src/lib/moderation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export enum MODERATOR_POWERS {
HANDLE_SCORE_CHEAT = 0b001,
HANDLE_ESCAPING = 0b010,
HANDLE_STALLING = 0b100,
SUSPEND = 0b1000,
}

export const MOD_POWER_NAMES: { [key in MODERATOR_POWERS]: string } = {
Expand All @@ -47,6 +48,7 @@ export const MOD_POWER_NAMES: { [key in MODERATOR_POWERS]: string } = {
"A label for a moderator power",
"Handle Stalling Reports",
),
[MODERATOR_POWERS.SUSPEND]: pgettext("A label for a moderator power", "Vote for Suspension"),
};

export function doAnnul(
Expand Down
45 changes: 26 additions & 19 deletions src/lib/report_manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { EventEmitter } from "eventemitter3";
import { emitNotification } from "@/components/Notifications";
import { browserHistory } from "@/lib/ogsHistory";
import { get, post } from "@/lib/requests";
import { MODERATOR_POWERS } from "./moderation";

export interface ReportRelation {
relationship: string;
Expand Down Expand Up @@ -87,6 +88,7 @@ class ReportManager extends EventEmitter<Events> {
const user = data.get("user");
report.id = parseInt(report.id as unknown as string);

console.log("updateIncidentReport", report);
if (!(report.id in this.active_incident_reports)) {
if (
data.get("user").is_moderator &&
Expand All @@ -109,11 +111,19 @@ class ReportManager extends EventEmitter<Events> {
}
}

if (
report.state === "resolved" ||
report.voters?.some((vote) => vote.voter_id === user.id) ||
(user.moderator_powers && report.escalated)
) {
// They voted if there is a vote from them (obviously) - but:
// if the report is escalated _and_ they have SUSPEND power, we are only interested in votes
// after the escalated_at time

const they_already_voted = report.voters?.some(
(vote) =>
vote.voter_id === user.id &&
(!report.escalated || // If the report is not escalated, any vote counts
!(user.moderator_powers & MODERATOR_POWERS.SUSPEND) || // If the user does not have SUSPEND powers, any vote counts
new Date(vote.updated) > new Date(report.escalated_at)), // If the user has SUSPEND powers, vote must be after escalation
);

if (report.state === "resolved" || they_already_voted) {
delete this.active_incident_reports[report.id];
this.this_user_reported_games = this.this_user_reported_games.filter(
(game_id) => game_id !== report.reported_game,
Expand Down Expand Up @@ -148,6 +158,7 @@ class ReportManager extends EventEmitter<Events> {
reports.sort(compare_reports);

this.sorted_active_incident_reports = reports;
console.log("active reports", reports.length, normal_ct);
this.emit("active-count", normal_ct);
this.emit("update");
}
Expand All @@ -162,7 +173,6 @@ class ReportManager extends EventEmitter<Events> {
// Clients should use getEligibleReports
private getAvailableReports(): Report[] {
const user = data.get("user");

return this.sorted_active_incident_reports.filter((report) => {
if (!report) {
return false;
Expand All @@ -185,22 +195,19 @@ class ReportManager extends EventEmitter<Events> {
// that they have not yet voted on, and are not escalated

if (user.moderator_powers && !community_mod_can_handle(user, report)) {
console.log("community_mod_can_handle reject", report.id, report.report_type);
return false;
}

const show_cm_reports = preferences.get("show-cm-reports");
if (!show_cm_reports) {
// don't hand community moderation reports to full mods unless the report is escalated,
// or they've asked to show them explicitly in settings,
// because community moderators are supposed to do these!
if (
user.is_moderator &&
!(report.moderator?.id === user.id) && // maybe they already have it, so they need to see it
["escaping", "score_cheating", "stalling"].includes(report.report_type) &&
!report.escalated
) {
return false;
}
// Don't offer community moderation reports to full mods, because community moderators do these.
// (The only way full moderators see CM-class reports is if they go hunting and claim them)
if (
user.is_moderator &&
!(report.moderator?.id === user.id) && // maybe they already have it, so they need to see it
["escaping", "score_cheating", "stalling"].includes(report.report_type) &&
!report.escalated
) {
return false;
}

// Never give a claimed report to community moderators
Expand Down
9 changes: 7 additions & 2 deletions src/lib/report_util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ReportType } from "@/components/Report";
interface Vote {
voter_id: number;
action: string;
updated: string;
}

export interface Report {
Expand All @@ -33,6 +34,7 @@ export interface Report {
updated: string;
state: string;
escalated: boolean;
escalated_at: string;
retyped: boolean;
source: string;
report_type: ReportType;
Expand Down Expand Up @@ -100,14 +102,17 @@ export function community_mod_has_power(

export function community_mod_can_handle(user: rest_api.UserConfig, report: Report): boolean {
// Community moderators only get to see reports that they have the power for and
// that they have not yet voted on, and are not escalated
// that they have not yet voted on... or if it's escalated, they must have suspend power

if (!user.moderator_powers) {
return false;
}

const they_already_voted = report.voters?.some((vote) => vote.voter_id === user.id);
const they_can_vote_to_suspend = user.moderator_powers & MODERATOR_POWERS.SUSPEND;
if (
community_mod_has_power(user.moderator_powers, report.report_type) &&
!(report.voters?.some((vote) => vote.voter_id === user.id) || report.escalated)
(!they_already_voted || (report.escalated && they_can_vote_to_suspend))
) {
return true;
}
Expand Down
4 changes: 3 additions & 1 deletion src/models/warning.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ declare namespace rest_api {
| "no_stalling_evident"
| "warn_duplicate_report"
| "report_type_changed"
| "bot_owner_notified";
| "bot_owner_notified"
| "ack_suspended"
| "ack_suspended_and_annul";

type Severity = "warning" | "acknowledgement" | "info";

Expand Down
8 changes: 8 additions & 0 deletions src/views/ReportsCenter/ModerationActionSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ const ACTION_PROMPTS = {
"Label for a moderator to select this option",
"Duplicate report - ask them not to do that.",
),

suspend_user: pgettext("Label for a moderator to select this option", "Suspend the user."),

suspend_user_and_annul: pgettext(
"Label for a moderator to select this option",
"Suspend user and annul game.",
),

// Note: keep this last, so it's positioned above the "note to moderator" input field
escalate: pgettext(
"A label for a community moderator to select this option - send report to to full moderators",
Expand Down
27 changes: 17 additions & 10 deletions src/views/ReportsCenter/ViewReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { ReportTypeSelector } from "./ReportTypeSelector";
import { alert } from "@/lib/swal_config";
import { ErrorBoundary } from "@/components/ErrorBoundary";
import * as DynamicHelp from "react-dynamic-help";
import { MODERATOR_POWERS } from "@/lib/moderation";

interface ViewReportProps {
reports: Report[];
Expand Down Expand Up @@ -77,6 +78,15 @@ export function ViewReport({ report_id, reports, onChange }: ViewReportProps): J
const { registerTargetItem } = React.useContext(DynamicHelp.Api);
const { ref: ignore_button } = registerTargetItem("ignore-button");

const captureReport = (report: Report) => {
setReport(report);
setModeratorId(report?.moderator?.id);
setReportState(report?.state);
setAnnulQueue(report?.detected_ai_games);
setAvailableActions(report?.available_actions);
setVoteCounts(report?.vote_counts);
};

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
Expand All @@ -86,12 +96,7 @@ export function ViewReport({ report_id, reports, onChange }: ViewReportProps): J
.getReport(report_id)
.then((report) => {
setError(null);
setReport(report);
setModeratorId(report?.moderator?.id);
setReportState(report?.state);
setAnnulQueue(report?.detected_ai_games);
setAvailableActions(report?.available_actions);
setVoteCounts(report?.vote_counts);
captureReport(report);
})
.catch((err) => {
console.error(err);
Expand All @@ -108,9 +113,7 @@ export function ViewReport({ report_id, reports, onChange }: ViewReportProps): J
React.useEffect(() => {
const onUpdate = (r: Report) => {
if (r.id === report?.id) {
setReport(r);
setModeratorId(r?.moderator?.id);
setReportState(r?.state);
captureReport(r);
}
};
report_manager.on("incident-report", onUpdate);
Expand Down Expand Up @@ -550,7 +553,11 @@ export function ViewReport({ report_id, reports, onChange }: ViewReportProps): J
.vote(report.id, action, note)
.then(() => next());
}}
enable={report.state === "pending" && !report.escalated}
enable={
report.state === "pending" &&
(!report.escalated ||
!!(user.moderator_powers & MODERATOR_POWERS.SUSPEND))
}
// clear the selection for subsequent reports
key={report.id}
report={report}
Expand Down
15 changes: 13 additions & 2 deletions src/views/Settings/ModeratorPreferences.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ export function ModeratorPreferences(_props: SettingGroupPageProps): JSX.Element
_setReportQuota(ev.target.value as any);
}
}

// At the moment we want moderators do non-CM reports
React.useEffect(() => {
if (show_cm_reports) {
setShowCMReports(false);
}
}, [show_cm_reports, setShowCMReports]);

if (!user.is_moderator && !user.moderator_powers) {
return null;
}
Expand Down Expand Up @@ -89,8 +97,11 @@ export function ModeratorPreferences(_props: SettingGroupPageProps): JSX.Element
<Toggle checked={hide_claimed_reports} onChange={setHideClaimedReports} />
</PreferenceLine>
<PreferenceLine title="Show un-escalated reports">
<Toggle checked={show_cm_reports} onChange={setShowCMReports} />
<span>This will include for you reports that CMs can still vote on</span>
<Toggle checked={false} onChange={() => {}} />
<span>
This would include for you reports that CMs can still vote on, but is
not currently available.
</span>
</PreferenceLine>
<PreferenceLine title="Join games anonymously">
<Toggle
Expand Down

0 comments on commit 22813ae

Please sign in to comment.