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-2979]: Add custom reason field to Other rejection reason #4436

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
@@ -1,5 +1,5 @@
.detailedExplanation {
margin-bottom: var(--spacing-3);
margin-bottom: var(--spacing-2);
}

.explanationLabel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => (
<Localized id="common-moderationReason-addExplanation">
<Button
onClick={onClick}
className={commonStyles.optionAction}
variant="none"
color="success"
className={cn(linkClassName, commonStyles.optionAction)}
variant="flat"
color="primary"
underline
>
+ Add explanation
</Button>
Expand All @@ -36,19 +41,26 @@ const AddExplanationButton: FunctionComponent<{ onClick: () => void }> = ({

const DetailedExplanation: FunctionComponent<Props> = ({
code,
value,
onChange,
explanationValue,
onChangeExplanation,
onBack,
customReasonValue,
onChangeCustomReason,
linkClassName,
}) => {
const [showAddExplanation, setShowAddExplanation] = useState(
!!(code === GQLREJECTION_REASON_CODE.OTHER)
);
const [showAddExplanation, setShowAddExplanation] = useState(false);

return (
<>
<Localized id="common-moderationReason-changeReason">
<Button className={styles.changeReason} variant="none" onClick={onBack}>
&lt; Change Reason
<Button
className={cn(linkClassName, styles.changeReason)}
variant="flat"
onClick={onBack}
color="primary"
underline
>
&lt; Change reason
</Button>
</Localized>

Expand All @@ -60,13 +72,36 @@ const DetailedExplanation: FunctionComponent<Props> = ({
<div className={styles.code}>{unsnake(code)}</div>
</Localized>

{code === GQLREJECTION_REASON_CODE.OTHER && (
<>
<Localized id="common-moderationReason-customReason">
<Label
className={cn(commonStyles.sectionLabel, styles.explanationLabel)}
>
Custom reason (required)
</Label>
</Localized>
<Localized
id="common-moderationReason-customReason-placeholder"
attrs={{ placeholder: true }}
>
<TextArea
className={styles.detailedExplanation}
placeholder="Add your reason"
value={customReasonValue || undefined}
onChange={(e) => onChangeCustomReason(e.target.value)}
/>
</Localized>
</>
)}

{showAddExplanation ? (
<>
<Localized id="common-moderationReason-detailedExplanation">
<Label
className={cn(commonStyles.sectionLabel, styles.explanationLabel)}
>
Explanation
Detailed explanation
</Label>
</Localized>

Expand All @@ -78,8 +113,8 @@ const DetailedExplanation: FunctionComponent<Props> = ({
className={styles.detailedExplanation}
placeholder="Add your explanation"
data-testid="moderation-reason-detailed-explanation"
value={value || undefined}
onChange={(e) => onChange(e.target.value)}
value={explanationValue || undefined}
onChange={(e) => onChangeExplanation(e.target.value)}
/>
</Localized>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
.optionAction {
padding: 0;
margin: var(--spacing-1) 0;
color: $colors-teal-600;
font-weight: var(--font-weight-secondary-regular);
text-decoration: underline;
}

.rejectButton {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ export interface Props {
onCancel: () => void;
onReason: (reason: Reason) => void;
id: string;
linkClassName?: string;
}

const ModerationReason: FunctionComponent<Props> = ({
onCancel,
onReason,
id,
linkClassName,
}) => {
const [view, setView] = useState<"REASON" | "EXPLANATION">("REASON");
const [reasonCode, setReasonCode] = useState<ReasonCode | null>(null);
Expand All @@ -32,6 +34,9 @@ const ModerationReason: FunctionComponent<Props> = ({
const [detailedExplanation, setDetailedExplanation] = useState<string | null>(
null
);
const [otherCustomReason, setOtherCustomReason] = useState<string | null>(
null
);

const submitReason = useCallback(() => {
onReason({
Expand All @@ -41,8 +46,15 @@ const ModerationReason: FunctionComponent<Props> = ({
? legalGrounds
: undefined,
detailedExplanation: detailedExplanation || undefined,
customReason: otherCustomReason || undefined,
});
}, [reasonCode, legalGrounds, detailedExplanation, onReason]);
}, [
reasonCode,
legalGrounds,
detailedExplanation,
onReason,
otherCustomReason,
]);

return (
<Box className={styles.root} data-testid={`moderation-reason-modal-${id}`}>
Expand All @@ -61,8 +73,11 @@ const ModerationReason: FunctionComponent<Props> = ({
setReasonCode(null);
}}
code={reasonCode!}
value={detailedExplanation}
onChange={setDetailedExplanation}
explanationValue={detailedExplanation}
onChangeExplanation={setDetailedExplanation}
customReasonValue={otherCustomReason}
onChangeCustomReason={setOtherCustomReason}
linkClassName={linkClassName}
/>
)}

Expand All @@ -75,7 +90,7 @@ const ModerationReason: FunctionComponent<Props> = ({
disabled={
reasonCode === null ||
(reasonCode === GQLREJECTION_REASON_CODE.OTHER &&
!detailedExplanation)
!otherCustomReason)
}
onClick={submitReason}
color="alert"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.link {
color: var(--palette-primary-600);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import UserBanPopoverContainer from "../UserBanPopover/UserBanPopoverContainer";
import ModerationActionsContainer from "./ModerationActionsContainer";
import RejectCommentMutation from "./RejectCommentMutation";

import styles from "./ModerationDropdownContainer.css";

export type ModerationDropdownView =
| "MODERATE"
| "REJECT_REASON"
Expand Down Expand Up @@ -108,6 +110,7 @@ const ModerationDropdownContainer: FunctionComponent<Props> = ({
id={comment.id}
onReason={reject}
onCancel={onDismiss}
linkClassName={styles.link}
/>
) : (
<UserBanPopoverContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ it("can copy comment embed code", async () => {
window.prompt = jsdomPrompt;
});

it("requires rection reason when dsaFeaturesEnabled", async () => {
it("requires rejection reason when dsaFeaturesEnabled", async () => {
await act(async () => {
await createTestRenderer({
resolvers: createResolversStub<GQLResolver>({
Expand All @@ -659,6 +659,7 @@ it("requires rection reason when dsaFeaturesEnabled", async () => {
reason: {
code: "OTHER",
detailedExplanation: "really weird comment tbh",
customReason: "custom reason",
},
});
return {
Expand Down Expand Up @@ -709,6 +710,12 @@ it("requires rection reason when dsaFeaturesEnabled", async () => {
});
expect(submitReasonButton).toBeDisabled();

const addAdditionalInfoButton = within(reasonModal).getByRole("button", {
name: "Add explanation",
});

fireEvent.click(addAdditionalInfoButton);

const additionalInfo = within(reasonModal).getByTestId(
"moderation-reason-detailed-explanation"
);
Expand All @@ -719,6 +726,17 @@ it("requires rection reason when dsaFeaturesEnabled", async () => {
});
});

expect(submitReasonButton).toBeDisabled();

const customReason =
within(reasonModal).getByPlaceholderText("Add your reason");

act(() => {
fireEvent.change(customReason, {
target: { value: "custom reason" },
});
});

expect(submitReasonButton).toBeEnabled();

act(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const createTestRenderer = async (
return { tabPane, applyButton, form };
};

it.only("change premod", async () => {
it("change premod", async () => {
const updateStorySettingsStub =
createMutationResolverStub<MutationToUpdateStorySettingsResolver>(
({ variables }) => {
Expand Down
3 changes: 3 additions & 0 deletions locales/en-US/common.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ common-moderationReason-detailedExplanation =
Detailed explanation
common-moderationReason-detailedExplanation-placeholder =
.placeholder = Add your explanation
common-moderationReason-customReason = Custom reason (required)
common-moderationReason-customReason-placeholder =
.placeholder = Add your reason

common-userBanned =
User was banned.
Expand Down
10 changes: 10 additions & 0 deletions 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 @@ -7327,6 +7332,11 @@ input RejectCommentReasonInput {
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
}

input RejectCommentInput {
Expand Down
1 change: 1 addition & 0 deletions server/src/core/server/models/action/moderation/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export interface CommentModerationAction extends TenantResource {
code: GQLREJECTION_REASON_CODE;
legalGrounds?: string;
detailedExplanation?: string;
customReason?: string;
};

/**
Expand Down
1 change: 1 addition & 0 deletions server/src/core/server/stacks/rejectComment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const rejectComment = async (
code: GQLREJECTION_REASON_CODE;
legalGrounds?: string | undefined;
detailedExplanation?: string | undefined;
customReason?: string | undefined;
},
request?: Request | undefined,
sendNotification = true,
Expand Down