diff --git a/INDEXES.md b/INDEXES.md index 77f282603a..2c8597d6c1 100644 --- a/INDEXES.md +++ b/INDEXES.md @@ -6,6 +6,22 @@ The goal of this document is to date-mark the indexes you add to support the cha If you are releasing, you can use this readme to check all the indexes prior to the release you are deploying and have a good idea of what indexes you might need to deploy to Mongo along with your release of a new Coral Docker image to kubernetes. +## 2023-11-24 + +``` +db.dsaReports.createIndex({ tenantID: 1, id: 1 }, { unique: true }); +``` + +- This index creates the uniqueness constraint for the `tenantID` and `id` fields on the `dsaReports` + +``` +db.dsaReports.createIndex({ status: 1, createdAt: 1, tenantID: 1 }); +db.dsaReports.createIndex({ referenceID: 1, tenantID: 1 }); +db.dsaReports.createIndex({ submissionID: 1, tenantID: 1 }); +``` + +- These indices are used to optimize pagination of `dsaReports` and allow them to be retrieved by their `referenceID`, `submissionID` efficiently. + ## 2023-10-18 ``` diff --git a/client/package-lock.json b/client/package-lock.json index 8fcf5140f5..d1b04e9cac 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "@coralproject/talk", - "version": "8.6.1", + "version": "8.6.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@coralproject/talk", - "version": "8.6.1", + "version": "8.6.2", "license": "Apache-2.0", "dependencies": { "@ampproject/toolbox-cache-url": "^2.9.0", diff --git a/client/package.json b/client/package.json index 7fea257341..a578f6c140 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@coralproject/talk", - "version": "8.6.1", + "version": "8.6.2", "author": "The Coral Project", "homepage": "https://coralproject.net/", "sideEffects": [ diff --git a/client/src/core/client/admin/components/ModerateCard/DecisionDetailsContainer.css b/client/src/core/client/admin/components/ModerateCard/DecisionDetailsContainer.css new file mode 100644 index 0000000000..cd2726c384 --- /dev/null +++ b/client/src/core/client/admin/components/ModerateCard/DecisionDetailsContainer.css @@ -0,0 +1,29 @@ +.wrapper { + background-color: var(--palette-grey-200); + font-family: var(--font-family-primary); +} + +.full { + width: 100%; +} + +.label { + font-size: var(--font-size-1); + font-weight: var(--font-weight-primary-semi-bold); + text-transform: uppercase; + color: var(--palette-grey-500); +} + +.rejected { + color: var(--palette-text-000); + background-color: var(--palette-error-500); + text-transform: uppercase; + padding: var(--spacing-1) var(--spacing-2); + width: fit-content; + font-size: var(--font-size-1); + font-weight: var(--font-weight-primary-semi-bold); +} + +.info { + font-size: var(--font-size-2); +} diff --git a/client/src/core/client/admin/components/ModerateCard/DecisionDetailsContainer.tsx b/client/src/core/client/admin/components/ModerateCard/DecisionDetailsContainer.tsx new file mode 100644 index 0000000000..c150805140 --- /dev/null +++ b/client/src/core/client/admin/components/ModerateCard/DecisionDetailsContainer.tsx @@ -0,0 +1,102 @@ +import { Localized } from "@fluent/react/compat"; +import React, { FunctionComponent } from "react"; +import { graphql } from "react-relay"; + +import { withFragmentContainer } from "coral-framework/lib/relay"; +import { Flex, HorizontalGutter, Timestamp } from "coral-ui/components/v2"; + +import { DecisionDetailsContainer_comment } from "coral-admin/__generated__/DecisionDetailsContainer_comment.graphql"; + +import styles from "./DecisionDetailsContainer.css"; + +import { unsnake } from "../ModerationReason/formatting"; + +interface Props { + comment: DecisionDetailsContainer_comment; +} + +const DecisionDetailsContainer: FunctionComponent = ({ comment }) => { + const statusHistory = comment.statusHistory.edges[0].node; + const { rejectionReason, createdAt } = statusHistory; + + return ( + + + + +
Decision
+
+ +
Rejected
+
+
+ {rejectionReason && rejectionReason.code && ( + + +
Reason
+
+ +
{unsnake(rejectionReason.code)}
+
+
+ )} +
+ {rejectionReason?.legalGrounds && ( + + +
Law broken
+
+
{rejectionReason?.legalGrounds}
+
+ )} + {rejectionReason?.customReason && ( + + +
Custom reason
+
+
{rejectionReason?.customReason}
+
+ )} + {rejectionReason?.detailedExplanation && ( + + +
Detailed explanation
+
+
+ {rejectionReason?.detailedExplanation} +
+
+ )} + +
+ {createdAt} +
+
+
+ ); +}; + +const enhanced = withFragmentContainer({ + comment: graphql` + fragment DecisionDetailsContainer_comment on Comment { + id + statusHistory(first: 1) { + edges { + node { + createdAt + rejectionReason { + code + legalGrounds + detailedExplanation + customReason + } + } + } + } + } + `, +})(DecisionDetailsContainer); + +export default enhanced; diff --git a/client/src/core/client/admin/components/ModerateCard/FlagDetails.tsx b/client/src/core/client/admin/components/ModerateCard/FlagDetails.tsx index 39d17bea81..ac1af0b224 100644 --- a/client/src/core/client/admin/components/ModerateCard/FlagDetails.tsx +++ b/client/src/core/client/admin/components/ModerateCard/FlagDetails.tsx @@ -10,6 +10,7 @@ interface Props { nodes: ReadonlyArray<{ flagger: { id: string; username: string | null } | null; additionalDetails: string | null; + reportID?: string | null; }>; onUsernameClick: (id?: string) => void; } @@ -39,6 +40,7 @@ const FlagDetails: FunctionComponent = ({ ) } details={flag.additionalDetails} + reportID={flag.reportID} /> ))} diff --git a/client/src/core/client/admin/components/ModerateCard/FlagDetailsContainer.tsx b/client/src/core/client/admin/components/ModerateCard/FlagDetailsContainer.tsx index bcb5dbbdfe..ba4604a500 100644 --- a/client/src/core/client/admin/components/ModerateCard/FlagDetailsContainer.tsx +++ b/client/src/core/client/admin/components/ModerateCard/FlagDetailsContainer.tsx @@ -70,12 +70,23 @@ const FlagDetailsContainer: FunctionComponent = ({ [comment.flags.nodes] ); + const illegalContent = comment.illegalContent.nodes; + return ( <>

Latest reports

+ + Illegal content + + } + nodes={illegalContent} + onUsernameClick={onUsernameClick} + /> @@ -139,6 +150,16 @@ const enhanced = withFragmentContainer({ additionalDetails } } + illegalContent { + nodes { + flagger { + username + id + } + additionalDetails + reportID + } + } revision { metadata { perspective { diff --git a/client/src/core/client/admin/components/ModerateCard/FlagDetailsEntry.css b/client/src/core/client/admin/components/ModerateCard/FlagDetailsEntry.css index b078dde2e9..6403096012 100644 --- a/client/src/core/client/admin/components/ModerateCard/FlagDetailsEntry.css +++ b/client/src/core/client/admin/components/ModerateCard/FlagDetailsEntry.css @@ -21,7 +21,7 @@ $moderateCardReasonTextColor: var(--palette-text-500); } .flagger { - margin-right: var(--mini-unit); + margin-right: var(--spacing-1); padding: var(--spacing-1); margin-left: calc(-1 * var(--spacing-1)); @@ -37,3 +37,7 @@ $moderateCardReasonTextColor: var(--palette-text-500); border-style: none; } } + +.viewReportButton { + padding: var(--spacing-1) !important; +} diff --git a/client/src/core/client/admin/components/ModerateCard/FlagDetailsEntry.tsx b/client/src/core/client/admin/components/ModerateCard/FlagDetailsEntry.tsx index 4732a1d447..de1a4797bb 100644 --- a/client/src/core/client/admin/components/ModerateCard/FlagDetailsEntry.tsx +++ b/client/src/core/client/admin/components/ModerateCard/FlagDetailsEntry.tsx @@ -1,6 +1,7 @@ +import { Localized } from "@fluent/react/compat"; import React, { FunctionComponent } from "react"; -import { BaseButton } from "coral-ui/components/v2"; +import { BaseButton, Button } from "coral-ui/components/v2"; import styles from "./FlagDetailsEntry.css"; @@ -8,12 +9,14 @@ interface Props { user: React.ReactNode; details?: React.ReactNode; onClick?: () => void; + reportID?: React.ReactNode; } const FlagDetailsEntry: FunctionComponent = ({ user, details, onClick, + reportID, }) => { return (
@@ -23,6 +26,18 @@ const FlagDetailsEntry: FunctionComponent = ({ )} {!onClick && {user}} + {reportID && ( + + + + )} {details && {details}}
); diff --git a/client/src/core/client/admin/components/ModerateCard/ModerateCardDetailsContainer.css b/client/src/core/client/admin/components/ModerateCard/ModerateCardDetailsContainer.css index 5886f8d3f8..86ecb9ca96 100644 --- a/client/src/core/client/admin/components/ModerateCard/ModerateCardDetailsContainer.css +++ b/client/src/core/client/admin/components/ModerateCard/ModerateCardDetailsContainer.css @@ -2,3 +2,14 @@ text-transform: uppercase; font-size: var(--font-size-2); } + +.decisionIcon { + height: 1.125rem; + width: 1.75rem; + margin: 0 var(--spacing-1) 0 0 !important; +} + +.decisionIcon svg { + height: 1.125rem; + width: 1.75rem; +} diff --git a/client/src/core/client/admin/components/ModerateCard/ModerateCardDetailsContainer.tsx b/client/src/core/client/admin/components/ModerateCard/ModerateCardDetailsContainer.tsx index 8dde7527e3..66b45c0c43 100644 --- a/client/src/core/client/admin/components/ModerateCard/ModerateCardDetailsContainer.tsx +++ b/client/src/core/client/admin/components/ModerateCard/ModerateCardDetailsContainer.tsx @@ -8,10 +8,12 @@ import React, { import { graphql } from "react-relay"; import { withFragmentContainer } from "coral-framework/lib/relay"; +import { GQLCOMMENT_STATUS } from "coral-framework/schema"; import { CheckDoubleIcon, LikeIcon, ListBulletsIcon, + ModerationDecisionIcon, PencilIcon, SvgIcon, } from "coral-ui/components/icons"; @@ -22,6 +24,7 @@ import { ModerateCardDetailsContainer_settings } from "coral-admin/__generated__ import AutomatedActionsContainer from "./AutomatedActionsContainer"; import CommentRevisionContainer from "./CommentRevisionContainer"; +import DecisionDetailsContainer from "./DecisionDetailsContainer"; import FlagDetailsContainer from "./FlagDetailsContainer"; import LinkDetailsContainer from "./LinkDetailsContainer"; import ReactionDetailsQuery from "./ReactionDetailsQuery"; @@ -34,7 +37,12 @@ interface Props { onUsernameClick: (id?: string) => void; } -type DetailsTabs = "INFO" | "REACTIONS" | "HISTORY" | "EXTERNAL_MOD"; +type DetailsTabs = + | "INFO" + | "REACTIONS" + | "HISTORY" + | "EXTERNAL_MOD" + | "DECISION"; function hasFlagDetails(c: ModerateCardDetailsContainer_comment) { return c.revision @@ -42,6 +50,7 @@ function hasFlagDetails(c: ModerateCardDetailsContainer_comment) { c.revision.actionCounts.flag.reasons.COMMENT_REPORTED_ABUSIVE + c.revision.actionCounts.flag.reasons.COMMENT_REPORTED_OTHER + c.revision.actionCounts.flag.reasons.COMMENT_REPORTED_BIO + + c.revision.actionCounts.illegal.total + c.revision.actionCounts.flag.reasons.COMMENT_REPORTED_SPAM > 0 || c.revision.metadata.perspective : false; @@ -52,7 +61,15 @@ const ModerateCardDetailsContainer: FunctionComponent = ({ onUsernameClick, settings, }) => { - const [activeTab, setActiveTab] = useState("INFO"); + const hasDecision = + comment.status === GQLCOMMENT_STATUS.REJECTED && + comment.statusHistory.edges[0] && + comment.statusHistory.edges[0].node.rejectionReason && + comment.statusHistory.edges[0].node.rejectionReason.code; + + const [activeTab, setActiveTab] = useState( + hasDecision ? "DECISION" : "INFO" + ); const onTabClick = useCallback( (id: string) => setActiveTab(id as DetailsTabs), @@ -79,6 +96,19 @@ const ModerateCardDetailsContainer: FunctionComponent = ({ return ( + {hasDecision && ( + + + + + Decision + + + + )} @@ -118,6 +148,9 @@ const ModerateCardDetailsContainer: FunctionComponent = ({ )} + {activeTab === "DECISION" && ( + + )} {activeTab === "INFO" && ( <> @@ -156,8 +189,20 @@ const enhanced = withFragmentContainer({ editing { edited } + statusHistory(first: 1) { + edges { + node { + rejectionReason { + code + } + } + } + } revision { actionCounts { + illegal { + total + } flag { reasons { COMMENT_REPORTED_OFFENSIVE @@ -193,6 +238,7 @@ const enhanced = withFragmentContainer({ ...CommentRevisionContainer_comment ...LinkDetailsContainer_comment ...AutomatedActionsContainer_comment + ...DecisionDetailsContainer_comment } `, settings: graphql` diff --git a/client/src/core/client/admin/components/ModerationReason/DetailedExplanation.css b/client/src/core/client/admin/components/ModerationReason/DetailedExplanation.css index c325296869..2076b91e0e 100644 --- a/client/src/core/client/admin/components/ModerationReason/DetailedExplanation.css +++ b/client/src/core/client/admin/components/ModerationReason/DetailedExplanation.css @@ -1,5 +1,5 @@ .detailedExplanation { - margin-bottom: var(--spacing-3); + margin-bottom: var(--spacing-2); } .explanationLabel { diff --git a/client/src/core/client/admin/components/ModerationReason/DetailedExplanation.tsx b/client/src/core/client/admin/components/ModerationReason/DetailedExplanation.tsx index 8c9ec0774a..cea272e759 100644 --- a/client/src/core/client/admin/components/ModerationReason/DetailedExplanation.tsx +++ b/client/src/core/client/admin/components/ModerationReason/DetailedExplanation.tsx @@ -13,21 +13,26 @@ import styles from "./DetailedExplanation.css"; import commonStyles from "./ModerationReason.css"; export interface Props { - onChange: (value: string) => void; + onChangeExplanation: (value: string) => void; + onChangeCustomReason: (value: string) => void; code: GQLREJECTION_REASON_CODE; - value: string | null; + explanationValue: string | null; + customReasonValue: string | null; onBack: () => void; + linkClassName?: string; } -const AddExplanationButton: FunctionComponent<{ onClick: () => void }> = ({ - onClick, -}) => ( +const AddExplanationButton: FunctionComponent<{ + onClick: () => void; + linkClassName?: string; +}> = ({ onClick, linkClassName }) => ( @@ -36,19 +41,26 @@ const AddExplanationButton: FunctionComponent<{ onClick: () => void }> = ({ const DetailedExplanation: FunctionComponent = ({ code, - value, - onChange, + explanationValue, + onChangeExplanation, onBack, + customReasonValue, + onChangeCustomReason, + linkClassName, }) => { - const [showAddExplanation, setShowAddExplanation] = useState( - !!(code === GQLREJECTION_REASON_CODE.OTHER) - ); + const [showAddExplanation, setShowAddExplanation] = useState(false); return ( <> - @@ -60,13 +72,36 @@ const DetailedExplanation: FunctionComponent = ({
{unsnake(code)}
+ {code === GQLREJECTION_REASON_CODE.OTHER && ( + <> + + + + +