Skip to content

Commit

Permalink
Merge pull request #4296 from coralproject/feat/CORL-2838-spam-ban-st…
Browse files Browse the repository at this point in the history
…ream-mod

[CORL-2838]: Spam ban via stream moderation
  • Loading branch information
tessalt committed Jul 25, 2023
2 parents 659a77e + 0930c34 commit 727beba
Show file tree
Hide file tree
Showing 23 changed files with 939 additions and 227 deletions.
47 changes: 30 additions & 17 deletions src/core/client/admin/components/BanModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Localized } from "@fluent/react/compat";
import { FORM_ERROR } from "final-form";
import React, {
FunctionComponent,
useCallback,
Expand Down Expand Up @@ -221,27 +222,39 @@ const BanModal: FunctionComponent<Props> = ({
const onFormSubmit = useCallback(async () => {
switch (updateType) {
case UpdateType.ALL_SITES:
await banUser({
userID, // Should be defined because the modal shouldn't open if author is null
message: customizeMessage ? emailMessage : getDefaultMessage,
rejectExistingComments,
siteIDs: viewerIsScoped
? viewer?.moderationScopes?.sites?.map(({ id }) => id)
: [],
});
try {
await banUser({
userID, // Should be defined because the modal shouldn't open if author is null
message: customizeMessage ? emailMessage : getDefaultMessage,
rejectExistingComments,
siteIDs: viewerIsScoped
? viewer?.moderationScopes?.sites?.map(({ id }) => id)
: [],
});
} catch (err) {
return { [FORM_ERROR]: err.message };
}
break;
case UpdateType.SPECIFIC_SITES:
await updateUserBan({
userID,
message: customizeMessage ? emailMessage : getDefaultMessage,
banSiteIDs,
unbanSiteIDs,
});
try {
await updateUserBan({
userID,
message: customizeMessage ? emailMessage : getDefaultMessage,
banSiteIDs,
unbanSiteIDs,
});
} catch (err) {
return { [FORM_ERROR]: err.message };
}
break;
case UpdateType.NO_SITES:
await removeUserBan({
userID,
});
try {
await removeUserBan({
userID,
});
} catch (err) {
return { [FORM_ERROR]: err.message };
}
}
if (banDomain) {
void createDomainBan({
Expand Down
4 changes: 4 additions & 0 deletions src/core/client/stream/local/local.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ extend type Comment {
# Remember last viewer action that could have caused a status change.
lastViewerAction: COMMENT_VIEWER_ACTION

# If the comment was spam banned and rejected. Used for showing spam banned
# confirmation in moderation rejected tombstone container.
spamBanned: Boolean

# If true then Comment came in live.
enteredLive: Boolean

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ export const CommentContainer: FunctionComponent<Props> = ({
<ModerationRejectedTombstoneContainer
comment={comment}
settings={settings}
story={story}
viewer={viewer!}
/>
);
}
Expand Down Expand Up @@ -808,6 +810,7 @@ const enhanced = withShowAuthPopupMutation(
...ReportFlowContainer_viewer
...ReportButton_viewer
...CaretContainer_viewer
...ModerationRejectedTombstoneContainer_viewer
}
`,
story: graphql`
Expand All @@ -829,6 +832,7 @@ const enhanced = withShowAuthPopupMutation(
...PermalinkButtonContainer_story
...ReplyCommentFormContainer_story
...UserTagsContainer_story
...ModerationRejectedTombstoneContainer_story
}
`,
comment: graphql`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Localized } from "@fluent/react/compat";
import cn from "classnames";
import React, { FunctionComponent } from "react";
import React, { FunctionComponent, useCallback } from "react";
import { graphql } from "react-relay";

import { withFragmentContainer } from "coral-framework/lib/relay";
import { useMutation, withFragmentContainer } from "coral-framework/lib/relay";
import CLASSES from "coral-stream/classes";
import {
ArrowsDownIcon,
Expand All @@ -17,7 +17,10 @@ import { CaretContainer_settings } from "coral-stream/__generated__/CaretContain
import { CaretContainer_story } from "coral-stream/__generated__/CaretContainer_story.graphql";
import { CaretContainer_viewer } from "coral-stream/__generated__/CaretContainer_viewer.graphql";

import ModerationDropdownContainer from "./ModerationDropdownContainer";
import { SetSpamBanned } from "../setSpamBanned";
import ModerationDropdownContainer, {
ModerationDropdownView,
} from "./ModerationDropdownContainer";

import styles from "./CaretContainer.css";

Expand All @@ -26,28 +29,44 @@ interface Props {
story: CaretContainer_story;
viewer: CaretContainer_viewer;
settings: CaretContainer_settings;
view?: ModerationDropdownView;
open?: boolean;
}

const CaretContainer: FunctionComponent<Props> = (props) => {
const popoverID = `comments-moderationMenu-${props.comment.id}`;
const setSpamBanned = useMutation(SetSpamBanned);
const setSpamBannedOnClickOutside = useCallback(() => {
if (props.comment.spamBanned) {
void setSpamBanned({ commentID: props.comment.id, spamBanned: false });
}
}, [props.comment.spamBanned, setSpamBanned, props.comment.id]);

return (
<Localized
id="comments-moderationDropdown-popover"
attrs={{ description: true }}
>
<Popover
id={popoverID}
visible={props.open}
placement="bottom-end"
description="A popover menu to moderate the comment"
body={({ toggleVisibility, scheduleUpdate }) => (
<ClickOutside onClickOutside={toggleVisibility}>
<ClickOutside
onClickOutside={() => {
setSpamBannedOnClickOutside();
toggleVisibility();
}}
>
<ModerationDropdownContainer
comment={props.comment}
story={props.story}
viewer={props.viewer}
settings={props.settings}
onDismiss={toggleVisibility}
scheduleUpdate={scheduleUpdate}
view={props.view}
/>
</ClickOutside>
)}
Expand Down Expand Up @@ -81,6 +100,7 @@ const enhanced = withFragmentContainer<Props>({
comment: graphql`
fragment CaretContainer_comment on Comment {
id
spamBanned
...ModerationDropdownContainer_comment
}
`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ const ModerationActionBanButton: FunctionComponent<Props> = ({
showSpinner = false,
}) => {
const localizationId = allSiteBan
? "comments-moderationDropdown-ban"
? "comments-moderationDropdown-spam-ban"
: "comments-moderationDropdown-siteBan";
const defaultText = allSiteBan ? "Ban User" : "Site Ban";
const defaultText = allSiteBan ? "Spam ban" : "Site ban";
const icon = allSiteBan
? SingleNeutralActionsBlockIcon
: AppWindowDisableIcon;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,25 +59,25 @@ const ModerationActionBanContainer: FunctionComponent<Props> = ({
<DropdownDivider />
{settings?.multisite ? (
<>
<ModerationActionBanButton
disabled={!user || !!userIsSiteBanned || userIsAllSiteBanned}
allSiteBan={false}
onClick={!user ? undefined : onSiteBan}
showSpinner={!user}
/>
{!viewerScoped && (
{viewerScoped && (
<ModerationActionBanButton
disabled={!user || userIsAllSiteBanned}
allSiteBan={true}
onClick={!user ? undefined : onBan}
disabled={!user || !!userIsSiteBanned || userIsAllSiteBanned}
allSiteBan={false}
onClick={!user ? undefined : onSiteBan}
showSpinner={!user}
/>
)}
<ModerationActionBanButton
disabled={!user}
allSiteBan={true}
onClick={!user ? undefined : onBan}
showSpinner={!user}
/>
</>
) : (
<>
<ModerationActionBanButton
disabled={!user || userIsAllSiteBanned}
disabled={!user}
allSiteBan={true}
onClick={!user ? undefined : onBan}
showSpinner={!user}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import { ModerationDropdownContainer_viewer } from "coral-stream/__generated__/M
import UserBanPopoverContainer from "../UserBanPopover/UserBanPopoverContainer";
import ModerationActionsContainer from "./ModerationActionsContainer";

type View = "MODERATE" | "BAN" | "SITE_BAN";
export type ModerationDropdownView =
| "MODERATE"
| "BAN"
| "SITE_BAN"
| "CONFIRM_BAN";

interface Props {
comment: ModerationDropdownContainer_comment;
Expand All @@ -29,6 +33,7 @@ interface Props {
settings: ModerationDropdownContainer_settings;
onDismiss: () => void;
scheduleUpdate: () => void;
view?: ModerationDropdownView;
}

const ModerationDropdownContainer: FunctionComponent<Props> = ({
Expand All @@ -38,9 +43,13 @@ const ModerationDropdownContainer: FunctionComponent<Props> = ({
settings,
onDismiss,
scheduleUpdate,
view: viewProp,
}) => {
const emitShowEvent = useViewerEvent(ShowModerationPopoverEvent);
const [view, setView] = useState<View>("MODERATE");
const [view, setView] = useState<ModerationDropdownView>(
viewProp ?? "MODERATE"
);

const onBan = useCallback(() => {
setView("BAN");
scheduleUpdate();
Expand Down Expand Up @@ -72,9 +81,11 @@ const ModerationDropdownContainer: FunctionComponent<Props> = ({
) : (
<UserBanPopoverContainer
comment={comment}
settings={settings}
story={story}
viewer={viewer}
onDismiss={onDismiss}
siteBan={view === "SITE_BAN"}
view={view}
/>
)}
</div>
Expand Down Expand Up @@ -110,11 +121,13 @@ const enhanced = withFragmentContainer<Props>({
settings: graphql`
fragment ModerationDropdownContainer_settings on Settings {
...ModerationActionsContainer_settings
...UserBanPopoverContainer_settings
}
`,
viewer: graphql`
fragment ModerationDropdownContainer_viewer on User {
...ModerationActionsContainer_viewer
...UserBanPopoverContainer_viewer
}
`,
})(ModerationDropdownContainer);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
.icon {
margin-left: var(--spacing-1);
}

.rejectedContainer {
margin-right: auto;
width: 100%;
}
Loading

0 comments on commit 727beba

Please sign in to comment.