Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incident report data delivery refactor #2977

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/components/AccountWarning/CannedMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ Thank you for your report, '{{reported}}' has been given a formal warning about
llm_pgettext(
"Acknowledgement message to a user",
`
Thank you for bringing the possible instance of '{{reported}}' abandoning the game to
our attention.
Thank you for bringing the possible instance of '{{reported}}' abandoning the game to our attention.

We looked into the game and did not see them failing to finish the game properly.

Expand Down
22 changes: 16 additions & 6 deletions src/components/IncidentReportTracker/IncidentReportCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,22 @@ import { Player } from "@/components/Player";

import { errorAlerter } from "@/lib/misc";
import { AutoTranslate } from "@/components/AutoTranslate";
import { Report } from "@/lib/report_util";
import { ReportNotification } from "@/lib/report_util";
import { useUser } from "@/lib/hooks";
import { report_categories } from "@/components/Report";
import { openReportedConversationModal } from "@/components/ReportedConversationModal";

function getReportType(report: Report): string {
export type ActionableReport = ReportNotification & {
unclaim: () => void;
good_report: () => void;
bad_report: () => void;
steal: () => void;
claim: () => void;
cancel: () => void;
set_note: () => void;
};

function getReportType(report: ActionableReport): string {
if (report.report_type === "appeal") {
return "Ban Appeal";
}
Expand All @@ -49,7 +59,7 @@ function getReportType(report: Report): string {
}

interface IncidentReportCardProps {
report: Report;
report: ActionableReport;
}

export function IncidentReportCard({ report }: IncidentReportCardProps): React.ReactElement {
Expand Down Expand Up @@ -178,13 +188,13 @@ export function IncidentReportCard({ report }: IncidentReportCardProps): React.R
</h3>
)}

{report.reported_conversation && (
{report.reported_conversation && report.reported_user && (
<div
className="spread"
onClick={() => {
openReportedConversationModal(
report.reported_user?.id,
report.reported_conversation,
report.reported_user!.id,
report.reported_conversation!,
);
}}
>
Expand Down
34 changes: 19 additions & 15 deletions src/components/IncidentReportTracker/IncidentReportList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,17 @@ import { alert } from "@/lib/swal_config";
import { post } from "@/lib/requests";

import { ignore, errorAlerter } from "@/lib/misc";
import { Report } from "@/lib/report_util";
import { ReportNotification } from "@/lib/report_util";

import { IncidentReportCard } from "./IncidentReportCard";
import { IncidentReportCard, ActionableReport } from "./IncidentReportCard";

// Define a type for the props
type IncidentReportListProps = {
reports: Report[];
reports: ReportNotification[];
modal?: boolean;
};

// This presents a list of incident reports with a summary and actions.
// It's intended to be built from Report Notifications that we get from the term server via report_manager
export function IncidentReportList({
reports,
modal = true,
Expand All @@ -40,14 +41,16 @@ export function IncidentReportList({
}

// Attach appropriate actions to each report
reports.forEach((report) => {
if (report.state !== "resolved") {
report.unclaim = () => {
const actionableReports: ActionableReport[] = reports.map((report) => {
const actionableReport = report as ActionableReport;

if (actionableReport.state !== "resolved") {
actionableReport.unclaim = () => {
post(`moderation/incident/${report.id}`, { id: report.id, action: "unclaim" })
.then(ignore)
.catch(errorAlerter);
};
report.good_report = () => {
actionableReport.good_report = () => {
post(`moderation/incident/${report.id}`, {
id: report.id,
action: "resolve",
Expand All @@ -56,7 +59,7 @@ export function IncidentReportList({
.then(ignore)
.catch(errorAlerter);
};
report.bad_report = () => {
actionableReport.bad_report = () => {
post(`moderation/incident/${report.id}`, {
id: report.id,
action: "resolve",
Expand All @@ -65,7 +68,7 @@ export function IncidentReportList({
.then(ignore)
.catch(errorAlerter);
};
report.steal = () => {
actionableReport.steal = () => {
post(`moderation/incident/${report.id}`, { id: report.id, action: "steal" })
.then((res) => {
if (res.vanished) {
Expand All @@ -74,7 +77,7 @@ export function IncidentReportList({
})
.catch(errorAlerter);
};
report.claim = () => {
actionableReport.claim = () => {
post(`moderation/incident/${report.id}`, { id: report.id, action: "claim" })
.then((res) => {
if (res.vanished) {
Expand All @@ -86,17 +89,17 @@ export function IncidentReportList({
})
.catch(errorAlerter);
};
report.cancel = () => {
actionableReport.cancel = () => {
post(`moderation/incident/${report.id}`, { id: report.id, action: "cancel" })
.then(ignore)
.catch(errorAlerter);
};

report.set_note = () => {
actionableReport.set_note = () => {
void alert
.fire({
input: "text",
inputValue: report.moderator_note,
inputValue: report.moderator_note || "",
showCancelButton: true,
})
.then(({ value: txt, isConfirmed }) => {
Expand All @@ -112,13 +115,14 @@ export function IncidentReportList({
});
};
}
return actionableReport;
});

return (
<div className="IncidentReportList">
{modal && <div className="IncidentReportList-backdrop" onClick={hideList}></div>}
<div className={modal ? "IncidentReportList-modal" : "IncidentReportList-plain"}>
{reports.map((report: Report, index) => (
{actionableReports.map((report: ActionableReport, index) => (
<IncidentReportCard key={index} report={report} />
))}
</div>
Expand Down
55 changes: 26 additions & 29 deletions src/lib/report_manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,22 @@ import { toast } from "@/lib/toast";
import { alert } from "@/lib/swal_config";
import { socket } from "@/lib/sockets";
import { pgettext } from "@/lib/translate";
import { Report, community_mod_can_handle } from "@/lib/report_util";
import { ReportNotification, community_mod_can_handle } from "@/lib/report_util";
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";

type ReportDetail = rest_api.moderation.ReportDetail;

export interface ReportRelation {
relationship: string;
report: Report;
report: ReportNotification;
}

interface Events {
"incident-report": (report: Report) => void;
"incident-report": (report: ReportNotification) => void;
"active-count": (count: number) => void;
update: () => void;
}
Expand All @@ -50,8 +52,8 @@ interface Events {
let post_connect_notification_squelch = true;

class ReportManager extends EventEmitter<Events> {
active_incident_reports: { [id: string]: Report } = {};
sorted_active_incident_reports: Report[] = [];
active_incident_reports: { [id: string]: ReportNotification } = {};
sorted_active_incident_reports: ReportNotification[] = [];
this_user_reported_games: number[] = [];

constructor() {
Expand All @@ -72,7 +74,7 @@ class ReportManager extends EventEmitter<Events> {
}

socket.on("incident-report", (report) =>
this.updateIncidentReport(report as any as Report),
this.updateIncidentReport(report as any as ReportNotification),
);

preferences.watch("moderator.report-settings", () => {
Expand All @@ -84,11 +86,10 @@ class ReportManager extends EventEmitter<Events> {
});
}

public updateIncidentReport(report: Report) {
public updateIncidentReport(report: ReportNotification) {
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 Down Expand Up @@ -143,7 +144,7 @@ class ReportManager extends EventEmitter<Events> {
const prefs = preferences.get("moderator.report-settings");
const user = data.get("user");

const reports: Report[] = [];
const reports: ReportNotification[] = [];
let normal_ct = 0;
for (const id in this.active_incident_reports) {
const report = this.active_incident_reports[id];
Expand All @@ -163,18 +164,18 @@ class ReportManager extends EventEmitter<Events> {
this.emit("update");
}

public getEligibleReports(): Report[] {
public getEligibleReports(): ReportNotification[] {
const quota = preferences.get("moderator.report-quota");
return !quota || this.getHandledTodayCount() < preferences.get("moderator.report-quota")
? this.getAvailableReports()
: // Always show the user their own reports
this.getAvailableReports().filter(
(report) => report.reporting_user.id === data.get("user").id,
(report) => report.reporting_user?.id === data.get("user").id,
);
}

// Clients should use getEligibleReports
private getAvailableReports(): Report[] {
private getAvailableReports(): ReportNotification[] {
const user = data.get("user");
return this.sorted_active_incident_reports.filter((report) => {
if (!report) {
Expand Down Expand Up @@ -286,11 +287,7 @@ class ReportManager extends EventEmitter<Events> {
this.update();
}

public async getReport(id: number): Promise<Report> {
if (id in this.active_incident_reports) {
return this.active_incident_reports[id];
}

public async getReportDetails(id: number): Promise<ReportDetail> {
const res = await get(`moderation/incident/${id}`);

if (res) {
Expand All @@ -300,15 +297,15 @@ class ReportManager extends EventEmitter<Events> {
throw new Error("Report not found");
}

public async reopen(report_id: number): Promise<Report> {
public async reopen(report_id: number): Promise<ReportNotification> {
const res = await post(`moderation/incident/${report_id}`, {
id: report_id,
action: "reopen",
});
this.updateIncidentReport(res);
return res;
}
public async close(report_id: number, helpful: boolean): Promise<Report> {
public async close(report_id: number, helpful: boolean): Promise<ReportNotification> {
delete this.active_incident_reports[report_id];
this.update();
const res = await post(`moderation/incident/${report_id}`, {
Expand All @@ -319,25 +316,25 @@ class ReportManager extends EventEmitter<Events> {
this.updateIncidentReport(res);
return res;
}
public async good_report(report_id: number): Promise<Report> {
public async good_report(report_id: number): Promise<ReportNotification> {
const res = await this.close(report_id, true);
this.updateIncidentReport(res);
return res;
}
public async bad_report(report_id: number): Promise<Report> {
public async bad_report(report_id: number): Promise<ReportNotification> {
const res = await this.close(report_id, false);
this.updateIncidentReport(res);
return res;
}
public async unclaim(report_id: number): Promise<Report> {
public async unclaim(report_id: number): Promise<ReportNotification> {
const res = await post(`moderation/incident/${report_id}`, {
id: report_id,
action: "unclaim",
});
this.updateIncidentReport(res);
return res;
}
public async claim(report_id: number): Promise<Report> {
public async claim(report_id: number): Promise<ReportNotification> {
const res = await post(`moderation/incident/${report_id}`, {
id: report_id,
action: "claim",
Expand All @@ -359,7 +356,7 @@ class ReportManager extends EventEmitter<Events> {
voted_action: string,
escalation_note: string,
dissenter_note: string,
): Promise<Report> {
): Promise<ReportNotification> {
return post(`moderation/incident/${report_id}`, {
action: "vote",
voted_action: voted_action,
Expand Down Expand Up @@ -397,7 +394,7 @@ class ReportManager extends EventEmitter<Events> {
}
}

function compare_reports(a: Report, b: Report): number {
function compare_reports(a: ReportNotification, b: ReportNotification): number {
const prefs = preferences.get("moderator.report-settings");
const sort_order = preferences.get("moderator.report-sort-order");
const user = data.get("user");
Expand All @@ -411,13 +408,13 @@ function compare_reports(a: Report, b: Report): number {
return custom_ordering || (sort_order === "newest-first" ? b.id - a.id : a.id - b.id);
}
if (a.moderator && !b.moderator) {
if (a.moderator.id === user.id) {
if (a.moderator?.id === user.id) {
return A_BEFORE_B;
}
return B_BEFORE_A;
}
if (b.moderator && !a.moderator) {
if (b.moderator.id === user.id) {
if (b.moderator?.id === user.id) {
return B_BEFORE_A;
}
return A_BEFORE_B;
Expand All @@ -426,10 +423,10 @@ function compare_reports(a: Report, b: Report): number {
// both have moderators, sort our mod reports first, then other
// mods, then by id

if (a.moderator.id !== user.id && b.moderator.id === user.id) {
if (a.moderator?.id !== user.id && b.moderator?.id === user.id) {
return B_BEFORE_A;
}
if (a.moderator.id === user.id && b.moderator.id !== user.id) {
if (a.moderator?.id === user.id && b.moderator?.id !== user.id) {
return A_BEFORE_B;
}

Expand Down
Loading