Revert "[CORL-382] Allow moderators to ban a commenter from the comment stream (#2378)" (#2384)

This reverts commit e77103d872.
This commit is contained in:
Wyatt Johnson
2019-07-02 21:59:18 +00:00
committed by GitHub
parent 414a4c2a42
commit 633998307d
14 changed files with 142 additions and 602 deletions
@@ -243,11 +243,7 @@ export class CommentContainer extends Component<Props, State> {
</Localized>
)}
{showCaret && (
<CaretContainer
comment={comment}
story={story}
viewer={viewer}
/>
<CaretContainer comment={comment} story={story} />
)}
</Flex>
}
@@ -333,7 +329,6 @@ const enhanced = withSetCommentIDMutation(
...UsernameWithPopoverContainer_viewer
...ReactionButtonContainer_viewer
...ReportButtonContainer_viewer
...CaretContainer_viewer
}
`,
story: graphql`
@@ -5,7 +5,6 @@ import { graphql } from "react-relay";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { CaretContainer_comment } from "coral-stream/__generated__/CaretContainer_comment.graphql";
import { CaretContainer_story } from "coral-stream/__generated__/CaretContainer_story.graphql";
import { CaretContainer_viewer } from "coral-stream/__generated__/CaretContainer_viewer.graphql";
import { Button, ClickOutside, Icon, Popover } from "coral-ui/components";
import ModerationDropdownContainer from "./ModerationDropdownContainer";
@@ -15,7 +14,6 @@ import styles from "./CaretContainer.css";
interface Props {
comment: CaretContainer_comment;
story: CaretContainer_story;
viewer: CaretContainer_viewer | null;
}
const CaretContainer: FunctionComponent<Props> = props => {
@@ -29,14 +27,12 @@ const CaretContainer: FunctionComponent<Props> = props => {
id={popoverID}
placement="bottom-end"
description="A popover menu to moderate the comment"
body={({ toggleVisibility, scheduleUpdate }) => (
body={({ toggleVisibility }) => (
<ClickOutside onClickOutside={toggleVisibility}>
<ModerationDropdownContainer
comment={props.comment}
story={props.story}
viewer={props.viewer}
onDismiss={toggleVisibility}
scheduleUpdate={scheduleUpdate}
/>
</ClickOutside>
)}
@@ -76,11 +72,6 @@ const enhanced = withFragmentContainer<Props>({
...ModerationDropdownContainer_story
}
`,
viewer: graphql`
fragment CaretContainer_viewer on User {
...ModerationDropdownContainer_viewer
}
`,
})(CaretContainer);
export default enhanced;
@@ -1,223 +0,0 @@
import cn from "classnames";
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent, useCallback } from "react";
import { graphql } from "react-relay";
import { useMutation, withFragmentContainer } from "coral-framework/lib/relay";
import { ModerationActionsContainer_comment } from "coral-stream/__generated__/ModerationActionsContainer_comment.graphql";
import { ModerationActionsContainer_story } from "coral-stream/__generated__/ModerationActionsContainer_story.graphql";
import { ModerationActionsContainer_viewer } from "coral-stream/__generated__/ModerationActionsContainer_viewer.graphql";
import { DropdownButton, DropdownDivider, Icon } from "coral-ui/components";
import ApproveCommentMutation from "./ApproveCommentMutation";
import FeatureCommentMutation from "./FeatureCommentMutation";
import RejectCommentMutation from "./RejectCommentMutation";
import UnfeatureCommentMutation from "./UnfeatureCommentMutation";
import styles from "./ModerationActionsContainer.css";
interface Props {
comment: ModerationActionsContainer_comment;
story: ModerationActionsContainer_story;
viewer: ModerationActionsContainer_viewer | null;
onDismiss: () => void;
onBan: () => void;
}
const ModerationActionsContainer: FunctionComponent<Props> = ({
comment,
story,
viewer,
onDismiss,
onBan,
}) => {
const approve = useMutation(ApproveCommentMutation);
const feature = useMutation(FeatureCommentMutation);
const unfeature = useMutation(UnfeatureCommentMutation);
const reject = useMutation(RejectCommentMutation);
const onApprove = useCallback(() => {
approve({ commentID: comment.id, commentRevisionID: comment.revision.id });
}, [approve, comment]);
const onReject = useCallback(
() =>
reject({ commentID: comment.id, commentRevisionID: comment.revision.id }),
[approve, comment]
);
const onFeature = useCallback(() => {
feature({
storyID: story.id,
commentID: comment.id,
commentRevisionID: comment.revision.id,
});
onDismiss();
}, [feature, onDismiss, story, comment]);
const onUnfeature = useCallback(() => {
unfeature({
commentID: comment.id,
storyID: story.id,
});
onDismiss();
}, [unfeature, onDismiss, story, comment]);
const approved = comment.status === "APPROVED";
const rejected = comment.status === "REJECTED";
const featured = comment.tags.some(t => t.code === "FEATURED");
const banned = comment.author!.status.ban.active;
const showBanOption =
viewer === null ? false : comment.author!.id !== viewer.id;
return (
<>
{featured ? (
<Localized id="comments-moderationDropdown-unfeature">
<DropdownButton
icon={
<Icon className={styles.featured} size="md">
star
</Icon>
}
className={styles.featured}
onClick={onUnfeature}
>
Un-Feature
</DropdownButton>
</Localized>
) : (
<Localized id="comments-moderationDropdown-feature">
<DropdownButton
icon={<Icon size="md">star_border</Icon>}
onClick={onFeature}
>
Feature
</DropdownButton>
</Localized>
)}
{approved ? (
<Localized id="comments-moderationDropdown-approved">
<DropdownButton
icon={
<Icon className={styles.approved} size="md">
check
</Icon>
}
className={styles.approved}
disabled
>
Approved
</DropdownButton>
</Localized>
) : (
<Localized id="comments-moderationDropdown-approve">
<DropdownButton
icon={<Icon size="md">check</Icon>}
onClick={onApprove}
>
Approve
</DropdownButton>
</Localized>
)}
{rejected ? (
<Localized id="comments-moderationDropdown-rejected">
<DropdownButton
icon={
<Icon className={styles.rejected} size="md">
close
</Icon>
}
className={styles.rejected}
disabled
>
Rejected
</DropdownButton>
</Localized>
) : (
<Localized id="comments-moderationDropdown-reject">
<DropdownButton
icon={<Icon size="md">close</Icon>}
onClick={onReject}
>
Reject
</DropdownButton>
</Localized>
)}
{showBanOption && (
<>
<DropdownDivider />
{banned ? (
<Localized id="comments-moderationDropdown-banned">
<DropdownButton
icon={
<div className={cn(styles.banIcon, styles.banned)}>
<Icon size="sm">block</Icon>
</div>
}
className={styles.banned}
disabled
>
Banned
</DropdownButton>
</Localized>
) : (
<Localized id="comments-moderationDropdown-ban">
<DropdownButton
icon={
<div className={styles.banIcon}>
<Icon size="sm">block</Icon>
</div>
}
onClick={onBan}
>
Ban User
</DropdownButton>
</Localized>
)}
</>
)}
<DropdownDivider />
<Localized id="comments-moderationDropdown-goToModerate">
<DropdownButton
href={`/admin/moderate/comment/${comment.id}`}
target="_blank"
anchor
>
Go to Moderate
</DropdownButton>
</Localized>
</>
);
};
const enhanced = withFragmentContainer<Props>({
comment: graphql`
fragment ModerationActionsContainer_comment on Comment {
id
author {
id
status {
ban {
active
}
}
}
revision {
id
}
status
tags {
code
}
}
`,
story: graphql`
fragment ModerationActionsContainer_story on Story {
id
}
`,
viewer: graphql`
fragment ModerationActionsContainer_viewer on User {
id
}
`,
})(ModerationActionsContainer);
export default enhanced;
@@ -10,13 +10,3 @@
color: var(--palette-primary-dark);
font-weight: var(--font-weight-bold);
}
.banned {
color: var(--palette-error-dark);
font-weight: var(--font-weight-bold);
}
.banIcon {
width: 18px;
height: 14px;
text-align: center;
line-height: 12px;
}
@@ -1,54 +1,153 @@
import React, { FunctionComponent, useCallback, useState } from "react";
import { Localized } from "fluent-react/compat";
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 { ModerationDropdownContainer_comment } from "coral-stream/__generated__/ModerationDropdownContainer_comment.graphql";
import { ModerationDropdownContainer_story } from "coral-stream/__generated__/ModerationDropdownContainer_story.graphql";
import { ModerationDropdownContainer_viewer } from "coral-stream/__generated__/ModerationDropdownContainer_viewer.graphql";
import { Dropdown } from "coral-ui/components";
import {
Dropdown,
DropdownButton,
DropdownDivider,
Icon,
} from "coral-ui/components";
import UserBanPopoverContainer from "../UserBanPopover/UserBanPopoverContainer";
import ModerationActionsContainer from "./ModerationActionsContainer";
import ApproveCommentMutation from "./ApproveCommentMutation";
import FeatureCommentMutation from "./FeatureCommentMutation";
import RejectCommentMutation from "./RejectCommentMutation";
import UnfeatureCommentMutation from "./UnfeatureCommentMutation";
type View = "MODERATE" | "BAN";
import styles from "./ModerationDropdownContainer.css";
interface Props {
comment: ModerationDropdownContainer_comment;
story: ModerationDropdownContainer_story;
viewer: ModerationDropdownContainer_viewer | null;
onDismiss: () => void;
scheduleUpdate: () => void;
}
const ModerationDropdownContainer: FunctionComponent<Props> = ({
comment,
story,
viewer,
onDismiss,
scheduleUpdate,
}) => {
const [view, setView] = useState<View>("MODERATE");
const onBan = useCallback(() => {
setView("BAN");
scheduleUpdate();
}, [setView, scheduleUpdate]);
const approve = useMutation(ApproveCommentMutation);
const feature = useMutation(FeatureCommentMutation);
const unfeature = useMutation(UnfeatureCommentMutation);
const reject = useMutation(RejectCommentMutation);
const onApprove = useCallback(() => {
approve({ commentID: comment.id, commentRevisionID: comment.revision.id });
}, [approve, comment]);
const onReject = useCallback(
() =>
reject({ commentID: comment.id, commentRevisionID: comment.revision.id }),
[approve, comment]
);
const onFeature = useCallback(() => {
feature({
storyID: story.id,
commentID: comment.id,
commentRevisionID: comment.revision.id,
});
onDismiss();
}, [feature, comment]);
const onUnfeature = useCallback(() => {
unfeature({
commentID: comment.id,
storyID: story.id,
});
onDismiss();
}, [unfeature, comment]);
const approved = comment.status === "APPROVED";
const rejected = comment.status === "REJECTED";
const featured = comment.tags.some(t => t.code === "FEATURED");
return (
<div>
{view === "MODERATE" ? (
<Dropdown>
<ModerationActionsContainer
comment={comment}
story={story}
viewer={viewer}
onDismiss={onDismiss}
onBan={onBan}
/>
</Dropdown>
<Dropdown>
{featured ? (
<Localized id="comments-moderationDropdown-unfeature">
<DropdownButton
icon={
<Icon className={styles.featured} size="md">
star
</Icon>
}
className={styles.featured}
onClick={onUnfeature}
>
Un-Feature
</DropdownButton>
</Localized>
) : (
<UserBanPopoverContainer user={comment.author!} onDismiss={onDismiss} />
<Localized id="comments-moderationDropdown-feature">
<DropdownButton
icon={<Icon size="md">star_border</Icon>}
onClick={onFeature}
>
Feature
</DropdownButton>
</Localized>
)}
</div>
{approved ? (
<Localized id="comments-moderationDropdown-approved">
<DropdownButton
icon={
<Icon className={styles.approved} size="md">
check
</Icon>
}
className={styles.approved}
disabled
>
Approved
</DropdownButton>
</Localized>
) : (
<Localized id="comments-moderationDropdown-approve">
<DropdownButton
icon={<Icon size="md">check</Icon>}
onClick={onApprove}
>
Approve
</DropdownButton>
</Localized>
)}
{rejected ? (
<Localized id="comments-moderationDropdown-rejected">
<DropdownButton
icon={
<Icon className={styles.rejected} size="md">
close
</Icon>
}
className={styles.rejected}
disabled
>
Rejected
</DropdownButton>
</Localized>
) : (
<Localized id="comments-moderationDropdown-reject">
<DropdownButton
icon={<Icon size="md">close</Icon>}
onClick={onReject}
>
Reject
</DropdownButton>
</Localized>
)}
<DropdownDivider />
<Localized id="comments-moderationDropdown-goToModerate">
<DropdownButton
href={`/admin/moderate/comment/${comment.id}`}
target="_blank"
anchor
>
Go to Moderate
</DropdownButton>
</Localized>
</Dropdown>
);
};
@@ -56,11 +155,6 @@ const enhanced = withFragmentContainer<Props>({
comment: graphql`
fragment ModerationDropdownContainer_comment on Comment {
id
author {
id
username
...UserBanPopoverContainer_user
}
revision {
id
}
@@ -68,18 +162,11 @@ const enhanced = withFragmentContainer<Props>({
tags {
code
}
...ModerationActionsContainer_comment
}
`,
story: graphql`
fragment ModerationDropdownContainer_story on Story {
id
...ModerationActionsContainer_story
}
`,
viewer: graphql`
fragment ModerationDropdownContainer_viewer on User {
...ModerationActionsContainer_viewer
}
`,
})(ModerationDropdownContainer);
@@ -180,12 +180,6 @@ function commit(
username: viewer.username,
createdAt: viewer.createdAt,
ignoreable: false,
status: {
current: viewer.status.current,
ban: {
active: viewer.status.ban.active,
},
},
},
body: input.body,
revision: {
@@ -1,62 +0,0 @@
import { graphql } from "react-relay";
import { Environment } from "relay-runtime";
import {
commitMutationPromiseNormalized,
createMutation,
lookup,
MutationInput,
} from "coral-framework/lib/relay";
import { GQLUser, GQLUSER_STATUS } from "coral-framework/schema";
import { BanUserMutation } from "coral-stream/__generated__/BanUserMutation.graphql";
let clientMutationId = 0;
const BanUserMutation = createMutation(
"banUser",
(environment: Environment, input: MutationInput<BanUserMutation>) => {
return commitMutationPromiseNormalized<BanUserMutation>(environment, {
mutation: graphql`
mutation BanUserMutation($input: BanUserInput!) {
banUser(input: $input) {
user {
id
status {
current
ban {
active
}
}
}
clientMutationId
}
}
`,
variables: {
input: {
...input,
clientMutationId: clientMutationId.toString(),
},
},
optimisticResponse: {
banUser: {
user: {
id: input.userID,
status: {
current: lookup<GQLUser>(
environment,
input.userID
)!.status.current.concat(GQLUSER_STATUS.BANNED),
ban: {
active: true,
},
},
},
clientMutationId: (clientMutationId++).toString(),
},
},
});
}
);
export default BanUserMutation;
@@ -1,4 +0,0 @@
.root {
width: 280px;
max-width: 80vw;
}
@@ -1,71 +0,0 @@
import { Localized } from "fluent-react/compat";
import React, { FunctionComponent, useCallback } from "react";
import { graphql } from "react-relay";
import { useMutation, withFragmentContainer } from "coral-framework/lib/relay";
import { UserBanPopoverContainer_user as UserData } from "coral-stream/__generated__/UserBanPopoverContainer_user.graphql";
import { Box, Button, Flex, Typography } from "coral-ui/components";
import BanUserMutation from "./BanUserMutation";
import styles from "./UserBanPopoverContainer.css";
interface Props {
onDismiss: () => void;
user: UserData;
}
const UserBanPopoverContainer: FunctionComponent<Props> = ({
user,
onDismiss,
}) => {
const banUser = useMutation(BanUserMutation);
const onBan = useCallback(() => {
banUser({ userID: user.id });
onDismiss();
}, [user, banUser, onDismiss]);
return (
<Box className={styles.root} p={3}>
<Localized id="comments-userBanPopover-title" $username={user.username}>
<Typography variant="heading3" mb={2}>
Ban {user.username}?
</Typography>
</Localized>
<Localized id="comments-userBanPopover-description">
<Typography variant="detail" mb={3}>
Once banned, this user will no longer be able to comment, use
reactions, or report comments.
</Typography>
</Localized>
<Flex justifyContent="flex-end" itemGutter="half">
<Localized id="comments-userBanPopover-cancel">
<Button variant="outlined" size="small" onClick={onDismiss}>
Cancel
</Button>
</Localized>
<Localized id="comments-userBanPopover-ban">
<Button variant="filled" size="small" onClick={onBan}>
Ban
</Button>
</Localized>
</Flex>
</Box>
);
};
const enhanced = withFragmentContainer<Props>({
user: graphql`
fragment UserBanPopoverContainer_user on User {
id
username
status {
current
ban {
active
}
}
}
`,
})(UserBanPopoverContainer);
export default enhanced;
@@ -150,12 +150,6 @@ function commit(
username: viewer.username,
createdAt: viewer.createdAt,
ignoreable: false,
status: {
current: viewer.status.current,
ban: {
active: viewer.status.ban.active,
},
},
},
revision: {
id: uuidGenerator(),
@@ -1,29 +1,20 @@
import timekeeper from "timekeeper";
import { pureMerge } from "coral-common/utils";
import { GQLResolver } from "coral-framework/schema";
import { GQLResolver, GQLUSER_STATUS } from "coral-framework/schema";
import {
createResolversStub,
CreateTestRendererParams,
waitForElement,
within,
} from "coral-framework/testHelpers";
import {
createComment,
createStory,
createUser,
createUserStatus,
} from "coral-stream/test/helpers/fixture";
import { settings } from "../../fixtures";
import { comments, settings, stories } from "../../fixtures";
import create from "./create";
const bannedUser = createUser();
bannedUser.status = createUserStatus(true);
const story = createStory();
const story = stories[0];
const firstComment = story.comments.edges[0].node;
const reactedComment = createComment();
const viewer = firstComment.author!;
async function createTestRenderer(
params: CreateTestRendererParams<GQLResolver> = {}
@@ -34,17 +25,22 @@ async function createTestRenderer(
createResolversStub<GQLResolver>({
Query: {
settings: () => settings,
viewer: () => bannedUser,
viewer: () =>
pureMerge<typeof viewer>(viewer, {
status: {
current: [GQLUSER_STATUS.BANNED],
},
}),
story: () =>
pureMerge<typeof story>(story, {
comments: {
edges: [
...story.comments.edges,
{
node: pureMerge<typeof reactedComment>(reactedComment, {
node: pureMerge<typeof comments[2]>(comments[2], {
actionCounts: { reaction: { total: 1 } },
}),
cursor: reactedComment.createdAt,
cursor: comments[2].createdAt,
},
],
},
-9
View File
@@ -90,15 +90,6 @@ export const baseUser = createFixture<GQLUser>({
createdAt: "2018-02-06T18:24:00.000Z",
status: {
current: [GQLUSER_STATUS.ACTIVE],
ban: {
active: false,
history: [],
},
suspension: {
active: false,
until: null,
history: [],
},
},
ignoredUsers: [],
comments: {
@@ -1,129 +0,0 @@
import {
GQLComment,
GQLCOMMENT_STATUS,
GQLMODERATION_MODE,
GQLStory,
GQLUser,
GQLUSER_ROLE,
GQLUSER_STATUS,
} from "coral-framework/schema";
import {
createFixture,
denormalizeComment,
denormalizeStory,
} from "coral-framework/testHelpers";
import uuid from "uuid/v4";
export function createDateInRange(start: Date, end: Date) {
return new Date(
start.getTime() + Math.random() * (end.getTime() - start.getTime())
);
}
export function randomDate() {
return createDateInRange(new Date(2000, 0, 1), new Date());
}
export function createUserStatus(banned: boolean = false) {
return {
current: [banned ? GQLUSER_STATUS.BANNED : GQLUSER_STATUS.ACTIVE],
ban: {
active: banned,
history: [],
},
suspension: {
active: false,
until: null,
history: [],
},
};
}
export function createUser() {
return createFixture<GQLUser>({
id: uuid(),
username: uuid(),
role: GQLUSER_ROLE.COMMENTER,
createdAt: randomDate().toISOString(),
status: createUserStatus(),
ignoredUsers: [],
comments: {
edges: [],
pageInfo: {
hasNextPage: false,
},
},
ignoreable: true,
});
}
export function createComment() {
const revision = uuid();
const createdAt = randomDate();
const editableUntil = new Date(createdAt.getTime() + 30 * 60000);
const author = createUser();
author.createdAt = new Date(createdAt.getTime() - 60 * 60000).toISOString();
return denormalizeComment(
createFixture<GQLComment>({
id: uuid(),
author,
body: uuid(),
revision: {
id: revision,
},
status: GQLCOMMENT_STATUS.NONE,
createdAt: createdAt.toISOString(),
replies: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } },
replyCount: 0,
editing: {
edited: false,
editableUntil: editableUntil.toISOString(),
},
actionCounts: {
reaction: {
total: 0,
},
},
tags: [],
})
);
}
export function createStory(createComments: boolean = true) {
const id = uuid();
const comments = [createComment(), createComment()];
return denormalizeStory(
createFixture<GQLStory>({
id,
url: `http://localhost/stories/story-${id}`,
comments: {
edges: [
{ node: comments[0], cursor: comments[0].createdAt },
{ node: comments[1], cursor: comments[1].createdAt },
],
pageInfo: {
hasNextPage: false,
},
},
metadata: {
title: uuid(),
},
isClosed: false,
commentCounts: {
totalVisible: 0,
tags: {
FEATURED: 0,
},
},
settings: {
moderation: GQLMODERATION_MODE.POST,
premodLinksEnable: false,
messageBox: {
enabled: false,
},
},
})
);
}
-9
View File
@@ -119,13 +119,6 @@ comments-userIgnorePopover-description =
comments-userIgnorePopover-ignore = Ignore
comments-userIgnorePopover-cancel = Cancel
comments-userBanPopover-title = Ban {$username}?
comments-userBanPopover-description =
Once banned, this user will no longer be able
to comment, use reactions, or report comments.
comments-userBanPopover-cancel = Cancel
comments-userBanPopover-ban = Ban
comments-moderationDropdown-popover =
.description = A popover menu to moderate the comment
comments-moderationDropdown-feature = Feature
@@ -134,8 +127,6 @@ comments-moderationDropdown-approve = Approve
comments-moderationDropdown-approved = Approved
comments-moderationDropdown-reject = Reject
comments-moderationDropdown-rejected = Rejected
comments-moderationDropdown-ban = Ban User
comments-moderationDropdown-banned = Banned
comments-moderationDropdown-goToModerate = Go to Moderate
comments-moderationDropdown-caretButton =
.aria-label = Moderate