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

[CORL-2978]: Add rejection reasons to comment card and user drawer #4439

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
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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<Props> = ({ comment }) => {
const statusHistory = comment.statusHistory.edges[0].node;
const { rejectionReason, createdAt } = statusHistory;

return (
<HorizontalGutter className={styles.wrapper} padding={3}>
<Flex>
<Flex direction="column" className={styles.full}>
<Localized id="moderate-decisionDetails-decisionLabel">
<div className={styles.label}>Decision</div>
</Localized>
<Localized id="moderate-decisionDetails-rejected">
<div className={styles.rejected}>Rejected</div>
</Localized>
</Flex>
{rejectionReason && rejectionReason.code && (
<Flex direction="column" className={styles.full}>
<Localized id="moderate-decisionDetails-reasonLabel">
<div className={styles.label}>Reason</div>
</Localized>
<Localized
id={`common-moderationReason-rejectionReason-${rejectionReason.code}`}
>
<div className={styles.info}>{unsnake(rejectionReason.code)}</div>
</Localized>
</Flex>
)}
</Flex>
{rejectionReason?.legalGrounds && (
<Flex direction="column">
<Localized id="moderate-decisionDetails-lawBrokenLabel">
<div className={styles.label}>Law broken</div>
</Localized>
<div className={styles.info}>{rejectionReason?.legalGrounds}</div>
</Flex>
)}
{rejectionReason?.customReason && (
<Flex direction="column">
<Localized id="moderate-decisionDetails-customReasonLabel">
<div className={styles.label}>Custom reason</div>
</Localized>
<div className={styles.info}>{rejectionReason?.customReason}</div>
</Flex>
)}
{rejectionReason?.detailedExplanation && (
<Flex direction="column">
<Localized id="moderate-decisionDetails-detailedExplanationLabel">
<div className={styles.label}>Detailed explanation</div>
</Localized>
<div className={styles.info}>
{rejectionReason?.detailedExplanation}
</div>
</Flex>
)}
<Flex>
<div className={styles.label}>
<Timestamp>{createdAt}</Timestamp>
</div>
</Flex>
</HorizontalGutter>
);
};

const enhanced = withFragmentContainer<Props>({
comment: graphql`
fragment DecisionDetailsContainer_comment on Comment {
id
statusHistory(first: 1) {
edges {
node {
createdAt
rejectionReason {
code
legalGrounds
detailedExplanation
customReason
}
}
}
}
}
`,
})(DecisionDetailsContainer);

export default enhanced;
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand All @@ -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
Expand All @@ -52,7 +60,15 @@ const ModerateCardDetailsContainer: FunctionComponent<Props> = ({
onUsernameClick,
settings,
}) => {
const [activeTab, setActiveTab] = useState<DetailsTabs>("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<DetailsTabs>(
hasDecision ? "DECISION" : "INFO"
);

const onTabClick = useCallback(
(id: string) => setActiveTab(id as DetailsTabs),
Expand All @@ -79,6 +95,19 @@ const ModerateCardDetailsContainer: FunctionComponent<Props> = ({
return (
<HorizontalGutter>
<TabBar variant="default" activeTab={activeTab} onTabClick={onTabClick}>
{hasDecision && (
<Tab tabID="DECISION" classes={styles}>
<Flex alignItems="center" itemGutter>
<SvgIcon
className={styles.decisionIcon}
Icon={ModerationDecisionIcon}
/>
<Localized id="moderateCardDetails-tab-decision">
<span>Decision</span>
</Localized>
</Flex>
</Tab>
)}
<Tab tabID="INFO" classes={styles}>
<Flex alignItems="center" itemGutter>
<SvgIcon Icon={ListBulletsIcon} />
Expand Down Expand Up @@ -118,6 +147,9 @@ const ModerateCardDetailsContainer: FunctionComponent<Props> = ({
</Tab>
)}
</TabBar>
{activeTab === "DECISION" && (
<DecisionDetailsContainer comment={comment} />
)}
{activeTab === "INFO" && (
<>
<LinkDetailsContainer comment={comment} settings={settings} />
Expand Down Expand Up @@ -156,6 +188,15 @@ const enhanced = withFragmentContainer<Props>({
editing {
edited
}
statusHistory(first: 1) {
edges {
node {
rejectionReason {
code
}
}
}
}
revision {
actionCounts {
flag {
Expand Down Expand Up @@ -193,6 +234,7 @@ const enhanced = withFragmentContainer<Props>({
...CommentRevisionContainer_comment
...LinkDetailsContainer_comment
...AutomatedActionsContainer_comment
...DecisionDetailsContainer_comment
}
`,
settings: graphql`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@ it("approves comment in reported queue", async () => {
edges: [
{
node: {
createdAt: "2023-06-01T14:21:21.890Z",
id: "mod-action",
status: GQLCOMMENT_STATUS.APPROVED,
moderator: {
Expand Down Expand Up @@ -727,6 +728,7 @@ it("rejects comment in reported queue", async () => {
edges: [
{
node: {
createdAt: "2023-06-01T14:21:21.890Z",
id: "mod-action",
status: GQLCOMMENT_STATUS.REJECTED,
moderator: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ it("approves comment in rejected queue", async () => {
edges: [
{
node: {
createdAt: "2023-06-01T14:21:21.890Z",
id: "mod-action",
status: GQLCOMMENT_STATUS.APPROVED,
moderator: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ it("approves single comment", async () => {
edges: [
{
node: {
createdAt: "2023-06-01T14:21:21.890Z",
id: "mod-action",
status: GQLCOMMENT_STATUS.APPROVED,
moderator: {
Expand Down Expand Up @@ -163,6 +164,7 @@ it("rejects single comment", async () => {
edges: [
{
node: {
createdAt: "2023-06-01T14:21:21.890Z",
id: "mod-action",
status: GQLCOMMENT_STATUS.REJECTED,
author: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { FunctionComponent } from "react";

const ModerationDecisionIcon: FunctionComponent = () => {
return (
<svg viewBox="0 0 30 18" xmlns="http://www.w3.org/2000/svg">
<path
stroke="currentColor"
d="M19.5218 9.10378L20.6125 10.0736L23.1824 8.06207"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M28.0738 4.4889L18.9097 1.04258C18.9097 1.04258 18.2988 0.812827 18.069 1.42377L13.9334 12.4207C13.9334 12.4207 13.7037 13.0316 14.3146 13.2613L23.4787 16.7077C23.4787 16.7077 24.0896 16.9374 24.3194 16.3265L28.455 5.32959C28.455 5.32959 28.6847 4.71865 28.0738 4.4889Z"
strokeLinecap="round"
strokeLinejoin="round"
stroke="currentColor"
/>
<path
d="M12.1194 14.6012L6.51917 16.7069C6.43887 16.7372 6.35336 16.7515 6.26755 16.7487C6.18173 16.746 6.0973 16.7264 6.01909 16.691C5.94087 16.6556 5.87041 16.6051 5.81174 16.5424C5.75307 16.4797 5.70734 16.4061 5.67718 16.3257L1.54159 5.32879C1.51142 5.24856 1.49735 5.16317 1.50018 5.0775C1.503 4.99183 1.52268 4.90756 1.55807 4.82949C1.59347 4.75143 1.64389 4.6811 1.70647 4.62252C1.76905 4.56394 1.84255 4.51826 1.92278 4.48809L11.0869 1.04177C11.2489 0.980839 11.4285 0.986767 11.5861 1.05825C11.7438 1.12974 11.8666 1.26093 11.9275 1.42296L13.9092 6.68251"
strokeLinecap="round"
strokeLinejoin="round"
stroke="currentColor"
/>
<path
d="M9.75537 6.77246L7.84945 10.9759"
strokeLinecap="round"
strokeLinejoin="round"
stroke="currentColor"
/>
<path
d="M6.70059 7.92139L10.9041 9.82731"
strokeLinecap="round"
strokeLinejoin="round"
stroke="currentColor"
/>
</svg>
);
};

export default ModerationDecisionIcon;
1 change: 1 addition & 0 deletions client/src/core/client/ui/components/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export { default as ListBulletsIcon } from "./ListBulletsIcon";
export { default as LockIcon } from "./LockIcon";
export { default as MessagesBubbleSquareIcon } from "./MessagesBubbleSquareIcon";
export { default as MessagesBubbleSquareWarningIcon } from "./MessagesBubbleSquareWarningIcon";
export { default as ModerationDecisionIcon } from "./ModerationDecisionIcon";
export { default as MultipleActionsChatIcon } from "./MultipleActionsChatIcon";
export { default as MultipleNeutralIcon } from "./MultipleNeutralIcon";
export { default as NavigationMenuHorizontalIcon } from "./NavigationMenuHorizontalIcon";
Expand Down
8 changes: 8 additions & 0 deletions locales/en-US/admin.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,13 @@ moderate-linkDetails-label = Copy link to this comment
moderate-in-stream-link-copy = In Stream
moderate-in-moderation-link-copy = In Moderation

moderate-decisionDetails-decisionLabel = Decision
moderate-decisionDetails-rejected = Rejected
moderate-decisionDetails-reasonLabel = Reason
moderate-decisionDetails-lawBrokenLabel = Law broken
moderate-decisionDetails-customReasonLabel = Custom reason
moderate-decisionDetails-detailedExplanationLabel = Detailed explanation

moderate-emptyQueue-pending = Nicely done! There are no more pending comments to moderate.
moderate-emptyQueue-reported = Nicely done! There are no more reported comments to moderate.
moderate-emptyQueue-unmoderated = Nicely done! All comments have been moderated.
Expand Down Expand Up @@ -1108,6 +1115,7 @@ moderate-searchBar-goTo = Go to
moderate-searchBar-seeAllResults = See all results

moderateCardDetails-tab-info = Info
moderateCardDetails-tab-decision = Decision
moderateCardDetails-tab-edits = Edit history
moderateCardDetails-tab-automatedActions = Automated actions
moderateCardDetails-tab-reactions = Reactions
Expand Down
12 changes: 11 additions & 1 deletion server/src/core/server/graph/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3459,6 +3459,11 @@ type RejectionReason {
detailedExplanation is any additional information the user wishes to provide.
"""
detailedExplanation: String

"""
customReason is a reason provided for rejection when the Other rejection code is selected.
"""
customReason: String
}

type CommentModerationAction {
Expand Down Expand Up @@ -3488,12 +3493,17 @@ type CommentModerationAction {
"""
reason is the reason the comment was rejected, if it was rejected
"""
reason: RejectionReason
rejectionReason: RejectionReason

"""
createdAt is the time that the CommentModerationAction was created.
"""
createdAt: Time!

"""
customReason is a reason provided for rejection when the Other rejection code is selected.
"""
customReason: String
}

type CommentModerationActionEdge {
Expand Down
Loading