From b5deac7b48ea00ec5c0c4e71531ba124efa334bd Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 19 Sep 2017 19:20:38 +0700 Subject: [PATCH 01/11] Bugfixes and show different notification for toxic comments Bugfixes: - Toxic comment flag was not added - GraphQL warnings for missing fields --- .../coral-embed-stream/src/graphql/index.js | 2 ++ client/talk-plugin-commentbox/CommentBox.js | 11 ++++++- graph/mutators/comment.js | 29 +++++++++++-------- .../client/components/CheckToxicityHook.js | 14 ++++++++- .../client/containers/CheckToxicityHook.js | 9 ++++++ .../client/index.js | 2 +- .../client/translations.yml | 4 +++ 7 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 plugins/talk-plugin-toxic-comments/client/containers/CheckToxicityHook.js diff --git a/client/coral-embed-stream/src/graphql/index.js b/client/coral-embed-stream/src/graphql/index.js index dc81b56a4..82d6ca9e3 100644 --- a/client/coral-embed-stream/src/graphql/index.js +++ b/client/coral-embed-stream/src/graphql/index.js @@ -123,6 +123,7 @@ export default { optimisticResponse: { createComment: { __typename: 'CreateCommentResponse', + errors: null, comment: { __typename: 'Comment', user: { @@ -132,6 +133,7 @@ export default { }, created_at: new Date().toISOString(), body, + actions: [], action_summaries: [], tags: tags.map((tag) => ({ tag: { diff --git a/client/talk-plugin-commentbox/CommentBox.js b/client/talk-plugin-commentbox/CommentBox.js index e5717e08a..409ad86ec 100644 --- a/client/talk-plugin-commentbox/CommentBox.js +++ b/client/talk-plugin-commentbox/CommentBox.js @@ -11,12 +11,20 @@ import {CommentForm} from './CommentForm'; export const name = 'talk-plugin-commentbox'; +const notifyReasons = ['LINKS', 'TRUST']; + +function shouldNotify(actions = []) { + return actions.some(({reason}) => notifyReasons.includes(reason)); +} + // Given a newly posted comment's status, show a notification to the user // if needed export const notifyForNewCommentStatus = (notify, comment) => { if (comment.status === 'REJECTED') { notify('error', t('comment_box.comment_post_banned_word')); - } else if (comment.status === 'PREMOD' || comment.status === 'SYSTEM_WITHHELD') { + } else if ( + comment.status === 'PREMOD' || + comment.status === 'SYSTEM_WITHHELD' && shouldNotify(comment.actions)) { notify('success', t('comment_box.comment_post_notif_premod')); } }; @@ -187,6 +195,7 @@ CommentBox.propTypes = { isReply: PropTypes.bool.isRequired, canPost: PropTypes.bool, notify: PropTypes.func.isRequired, + commentBox: PropTypes.object, }; const mapStateToProps = ({commentBox}) => ({commentBox}); diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 75cad99b7..2aee766a5 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -393,13 +393,12 @@ const moderationPhases = [ ]; /** - * This resolves a given comment's status to take into account moderator actions - * are applied. + * This resolves a given comment's status and actions. * @param {Object} context graphql context * @param {String} body body of the comment * @param {String} [asset_id] asset for the comment * @param {Object} [wordlist={}] the results of the wordlist scan - * @return {Promise} resolves to the comment's status + * @return {Object} resolves to the comment's status and actions */ const resolveCommentModeration = async (context, comment) => { @@ -418,6 +417,8 @@ const resolveCommentModeration = async (context, comment) => { // Combine the asset and the settings to get the asset settings. const assetSettings = await AssetsService.rectifySettings(asset, settings); + let actions = comment.actions || []; + // Loop over all the moderation phases and see if we've resolved the status. for (const phase of moderationPhases) { const result = await phase(context, comment, { @@ -429,13 +430,14 @@ const resolveCommentModeration = async (context, comment) => { if (result) { - // Merge the comment and the result together. - comment = merge(comment, result); + if (result.actions) { + actions.push(...result.actions); + } // If this result contained a status, then we've finished resolving // phases! if (result.status) { - return comment.actions; + return {status: result.status, actions}; } } } @@ -454,17 +456,20 @@ const createPublicComment = async (context, comment) => { // We then take the wordlist and the comment into consideration when // considering what status to assign the new comment, and resolve the new // status to set the comment to. - let actions = await resolveCommentModeration(context, comment); + let {actions, status} = await resolveCommentModeration(context, comment); + + // Assign status to comment. + comment.status = status; // Then we actually create the comment with the new status. - comment = await createComment(context, comment); + const result = await createComment(context, comment); // Create all the actions that were determined during the moderation check // phase. - await createActions(comment.id, actions); + await createActions(result.id, actions); // Finally, we return the comment. - return comment; + return result; }; // createActions will for each of the provided actions, create the given action @@ -515,10 +520,10 @@ const edit = async (context, {id, asset_id, edit: {body}}) => { let comment = {id, asset_id, body}; // Determine the new status of the comment. - const actions = await resolveCommentModeration(context, comment); + const {actions, status} = await resolveCommentModeration(context, comment); // Execute the edit. - comment = await CommentsService.edit({id, author_id: context.user.id, body, status: comment.status}); + comment = await CommentsService.edit({id, author_id: context.user.id, body, status}); // Create all the actions that were determined during the moderation check // phase. diff --git a/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js index 22c378435..8572bdd5a 100644 --- a/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js +++ b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js @@ -1,4 +1,6 @@ import React from 'react'; +import PropTypes from 'prop-types'; +import {t} from 'plugin-api/beta/client/services'; /** * CheckToxicityHook adds hooks to the `commentBox` @@ -20,7 +22,11 @@ export default class CheckToxicityHook extends React.Component { } }); - this.toxicityPostHook = this.props.registerHook('postSubmit', () => { + this.toxicityPostHook = this.props.registerHook('postSubmit', (result) => { + const actions = result.createComment.comment && result.createComment.comment.actions; + if (actions && actions.some(({reason}) => reason === 'TOXIC_COMMENT')) { + this.props.notify('error', t('talk-plugin-toxic-comments.still_toxic')); + } // Reset `checked` after comment was successfully posted. this.checked = false; @@ -36,3 +42,9 @@ export default class CheckToxicityHook extends React.Component { return null; } } + +CheckToxicityHook.propTypes = { + notify: PropTypes.func.isRequired, + registerHook: PropTypes.func.isRequired, + unregisterHook: PropTypes.func.isRequired, +}; diff --git a/plugins/talk-plugin-toxic-comments/client/containers/CheckToxicityHook.js b/plugins/talk-plugin-toxic-comments/client/containers/CheckToxicityHook.js new file mode 100644 index 000000000..f2b010b0d --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/client/containers/CheckToxicityHook.js @@ -0,0 +1,9 @@ +import {bindActionCreators} from 'redux'; +import {connect} from 'plugin-api/beta/client/hocs'; +import {notify} from 'plugin-api/beta/client/actions/notification'; +import CheckToxicityHook from '../components/CheckToxicityHook'; + +const mapDispatchToProps = (dispatch) => + bindActionCreators({notify}, dispatch); + +export default connect(null, mapDispatchToProps)(CheckToxicityHook); diff --git a/plugins/talk-plugin-toxic-comments/client/index.js b/plugins/talk-plugin-toxic-comments/client/index.js index c364a2a00..bb0417606 100644 --- a/plugins/talk-plugin-toxic-comments/client/index.js +++ b/plugins/talk-plugin-toxic-comments/client/index.js @@ -1,5 +1,5 @@ import translations from './translations.yml'; -import CheckToxicityHook from './components/CheckToxicityHook'; +import CheckToxicityHook from './containers/CheckToxicityHook'; export default { translations, diff --git a/plugins/talk-plugin-toxic-comments/client/translations.yml b/plugins/talk-plugin-toxic-comments/client/translations.yml index ebcc31903..787095046 100644 --- a/plugins/talk-plugin-toxic-comments/client/translations.yml +++ b/plugins/talk-plugin-toxic-comments/client/translations.yml @@ -3,4 +3,8 @@ en: COMMENT_IS_TOXIC: | Are you sure? The language in this comment might violate our community guidelines. You can edit the comment or submit it for moderator review. + talk-plugin-toxic-comments: + still_toxic: | + This edited comment might still violate our community guidelines. + Our moderation team will review your comment shortly. es: From 563a6d23dd0125fbc192518b51722c5665dd6a73 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 19 Sep 2017 21:43:58 +0700 Subject: [PATCH 02/11] First pass adding labels --- .../src/components/CommentLabels.css | 2 + .../src/components/CommentLabels.js | 39 +++++++++++++++++++ .../coral-admin/src/components/FlagLabel.css | 4 ++ .../coral-admin/src/components/FlagLabel.js | 21 ++++++++++ client/coral-admin/src/components/Label.css | 26 +++++++++++++ client/coral-admin/src/components/Label.js | 22 +++++++++++ .../routes/Moderation/components/Comment.js | 12 +++--- 7 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 client/coral-admin/src/components/CommentLabels.css create mode 100644 client/coral-admin/src/components/CommentLabels.js create mode 100644 client/coral-admin/src/components/FlagLabel.css create mode 100644 client/coral-admin/src/components/FlagLabel.js create mode 100644 client/coral-admin/src/components/Label.css create mode 100644 client/coral-admin/src/components/Label.js diff --git a/client/coral-admin/src/components/CommentLabels.css b/client/coral-admin/src/components/CommentLabels.css new file mode 100644 index 000000000..c3a2af639 --- /dev/null +++ b/client/coral-admin/src/components/CommentLabels.css @@ -0,0 +1,2 @@ +.root { +} diff --git a/client/coral-admin/src/components/CommentLabels.js b/client/coral-admin/src/components/CommentLabels.js new file mode 100644 index 000000000..631cf3443 --- /dev/null +++ b/client/coral-admin/src/components/CommentLabels.js @@ -0,0 +1,39 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './CommentLabels.css'; +import Label from './Label'; +import FlagLabel from './FlagLabel'; +import cn from 'classnames'; + +function isUserFlagged(actions) { + return actions.some((action) => action.__typename === 'FlagAction' && action.user); +} + +function hasSuspectedWords(actions) { + return actions.some((action) => action.__typename === 'FlagAction' && action.reason === 'Matched suspect word filter'); +} + +function hasHistoryFlag(actions) { + return actions.some((action) => action.__typename === 'FlagAction' && action.reason === 'TRUST'); +} + +const CommentLabels = ({className, status, actions, isReply}) => { + return ( +
+ {isReply && } + {status === 'PREMOD' && } + {isUserFlagged(actions) && User} + {hasSuspectedWords(actions) && Suspect} + {hasHistoryFlag(actions) && History} +
+ ); +}; + +CommentLabels.propTypes = { + className: PropTypes.string, + status: PropTypes.string, + actions: PropTypes.array, + isReply: PropTypes.bool, +}; + +export default CommentLabels; diff --git a/client/coral-admin/src/components/FlagLabel.css b/client/coral-admin/src/components/FlagLabel.css new file mode 100644 index 000000000..070e117d1 --- /dev/null +++ b/client/coral-admin/src/components/FlagLabel.css @@ -0,0 +1,4 @@ +.flag { + background: #d03235; +} + diff --git a/client/coral-admin/src/components/FlagLabel.js b/client/coral-admin/src/components/FlagLabel.js new file mode 100644 index 000000000..8a7864c8b --- /dev/null +++ b/client/coral-admin/src/components/FlagLabel.js @@ -0,0 +1,21 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './FlagLabel.css'; +import Label from './Label'; +import cn from 'classnames'; + +const FlagLabel = ({iconName, children, className}) => { + return ( + + ); +}; + +FlagLabel.propTypes = { + className: PropTypes.string, + children: PropTypes.node.isRequired, + iconName: PropTypes.string, +}; + +export default FlagLabel; diff --git a/client/coral-admin/src/components/Label.css b/client/coral-admin/src/components/Label.css new file mode 100644 index 000000000..fb6bd86ff --- /dev/null +++ b/client/coral-admin/src/components/Label.css @@ -0,0 +1,26 @@ +.root { + display: inline-block; + color: white; + background: grey; + box-sizing: border-box; + padding: 2px 5px; + font-size: 12px; + height: 24px; + letter-spacing: 0.4px; + line-height: 22px; + background: #063B9A; + min-width: 80px; + text-align: center; +} + +.icon { + font-size: 14px; + vertical-align: text-top; + margin: 0; + margin-right: 4px; +} + +.isFlag { + background: #d03235; +} + diff --git a/client/coral-admin/src/components/Label.js b/client/coral-admin/src/components/Label.js new file mode 100644 index 000000000..9d1b736de --- /dev/null +++ b/client/coral-admin/src/components/Label.js @@ -0,0 +1,22 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './Label.css'; +import {Icon} from 'coral-ui'; +import cn from 'classnames'; + +const Label = ({iconName, children, className, isFlag}) => { + return ( + + {children} + + ); +}; + +Label.propTypes = { + className: PropTypes.string, + isFlag: PropTypes.bool, + children: PropTypes.node.isRequired, + iconName: PropTypes.string, +}; + +export default Label; diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index a3edb4196..ce055e644 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -3,10 +3,9 @@ import PropTypes from 'prop-types'; import {Link} from 'react-router'; import {Icon} from 'coral-ui'; -import ReplyBadge from 'coral-admin/src/components/ReplyBadge'; import FlagBox from 'coral-admin/src/components/FlagBox'; import styles from './styles.css'; -import CommentType from 'coral-admin/src/components/CommentType'; +import CommentLabels from 'coral-admin/src/components/CommentLabels'; import CommentAnimatedEdit from 'coral-admin/src/components/CommentAnimatedEdit'; import Slot from 'coral-framework/components/Slot'; import {getActionSummary} from 'coral-framework/utils'; @@ -16,7 +15,6 @@ import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem'; import CommentBodyHighlighter from 'coral-admin/src/components/CommentBodyHighlighter'; import IfHasLink from 'coral-admin/src/components/IfHasLink'; import cn from 'classnames'; -import {getCommentType} from 'coral-admin/src/utils/comment'; import t, {timeago} from 'coral-framework/services/i18n'; @@ -66,7 +64,6 @@ class Comment extends React.Component { const flagActionSummaries = getActionSummary('FlagActionSummary', comment); const flagActions = comment.actions && comment.actions.filter((a) => a.__typename === 'FlagAction'); - const commentType = getCommentType(comment); const selectionStateCSS = selected ? 'mdl-shadow--16dp' : 'mdl-shadow--2dp'; @@ -109,8 +106,11 @@ class Comment extends React.Component { }
- {comment.hasParent && } - + Date: Tue, 19 Sep 2017 22:29:51 +0700 Subject: [PATCH 03/11] Added core labels --- .../src/components/CommentLabels.css | 11 +++++++ .../src/components/CommentLabels.js | 18 ++++++----- .../src/components/CommentType.css | 30 ----------------- .../coral-admin/src/components/CommentType.js | 32 ------------------- client/coral-admin/src/components/Label.css | 4 --- .../coral-admin/src/components/ReplyBadge.js | 11 ------- .../src/components/UserDetailComment.css | 2 +- .../src/components/UserDetailComment.js | 10 ++---- .../src/containers/CommentLabels.js | 21 ++++++++++++ .../src/containers/UserDetailComment.js | 4 +++ .../routes/Moderation/components/Comment.js | 4 +-- .../routes/Moderation/containers/Comment.js | 5 ++- client/coral-admin/src/utils/comment.js | 9 ------ client/coral-ui/components/Badge.css | 20 ------------ client/coral-ui/components/Badge.js | 13 -------- client/coral-ui/index.js | 1 - 16 files changed, 55 insertions(+), 140 deletions(-) delete mode 100644 client/coral-admin/src/components/CommentType.css delete mode 100644 client/coral-admin/src/components/CommentType.js delete mode 100644 client/coral-admin/src/components/ReplyBadge.js create mode 100644 client/coral-admin/src/containers/CommentLabels.js delete mode 100644 client/coral-admin/src/utils/comment.js delete mode 100644 client/coral-ui/components/Badge.css delete mode 100644 client/coral-ui/components/Badge.js diff --git a/client/coral-admin/src/components/CommentLabels.css b/client/coral-admin/src/components/CommentLabels.css index c3a2af639..718cd7dfd 100644 --- a/client/coral-admin/src/components/CommentLabels.css +++ b/client/coral-admin/src/components/CommentLabels.css @@ -1,2 +1,13 @@ .root { + > *:not(:last-child) { + margin-right: 3px; + } +} + +.replyLabel { + background-color: #3D73D5; +} + +.premodLabel { + background-color: #063B9A; } diff --git a/client/coral-admin/src/components/CommentLabels.js b/client/coral-admin/src/components/CommentLabels.js index 631cf3443..333f905ef 100644 --- a/client/coral-admin/src/components/CommentLabels.js +++ b/client/coral-admin/src/components/CommentLabels.js @@ -1,9 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import styles from './CommentLabels.css'; import Label from './Label'; import FlagLabel from './FlagLabel'; import cn from 'classnames'; +import styles from './CommentLabels.css'; function isUserFlagged(actions) { return actions.some((action) => action.__typename === 'FlagAction' && action.user); @@ -17,11 +17,11 @@ function hasHistoryFlag(actions) { return actions.some((action) => action.__typename === 'FlagAction' && action.reason === 'TRUST'); } -const CommentLabels = ({className, status, actions, isReply}) => { +const CommentLabels = ({comment: {className, status, actions, hasParent}}) => { return (
- {isReply && } - {status === 'PREMOD' && } + {hasParent && } + {status === 'PREMOD' && } {isUserFlagged(actions) && User} {hasSuspectedWords(actions) && Suspect} {hasHistoryFlag(actions) && History} @@ -30,10 +30,12 @@ const CommentLabels = ({className, status, actions, isReply}) => { }; CommentLabels.propTypes = { - className: PropTypes.string, - status: PropTypes.string, - actions: PropTypes.array, - isReply: PropTypes.bool, + comment: PropTypes.shape({ + className: PropTypes.string, + status: PropTypes.string, + actions: PropTypes.array, + hasParent: PropTypes.bool, + }), }; export default CommentLabels; diff --git a/client/coral-admin/src/components/CommentType.css b/client/coral-admin/src/components/CommentType.css deleted file mode 100644 index 969d9511d..000000000 --- a/client/coral-admin/src/components/CommentType.css +++ /dev/null @@ -1,30 +0,0 @@ -.commentType { - display: inline-block; - color: white; - background: grey; - box-sizing: border-box; - padding: 2px 5px; - font-size: 12px; - height: 24px; - letter-spacing: 0.4px; - line-height: 22px; - - > i { - font-size: 14px; - vertical-align: text-top; - margin: 0; - margin-right: 4px; - } - - &.premod { - background: #063B9A; - } - - &.flagged { - background: #d03235; - } - - &.no-type { - display: none; - } -} diff --git a/client/coral-admin/src/components/CommentType.js b/client/coral-admin/src/components/CommentType.js deleted file mode 100644 index 4d1ff7e67..000000000 --- a/client/coral-admin/src/components/CommentType.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import styles from './CommentType.css'; -import {Icon} from 'coral-ui'; -import cn from 'classnames'; - -const CommentType = (props) => { - const typeData = getTypeData(props.type); - - return ( - - {typeData.text} - - ); -}; - -const getTypeData = (type) => { - switch (type) { - case 'premod': - return {icon: 'query_builder', text: 'Pre-Mod', className: 'premod'}; - case 'flagged': - return {icon: 'flag', text: 'Flagged', className: 'flagged'}; - default: - return {icon: 'flag', className: 'no-type'}; - } -}; - -CommentType.propTypes = { - type: PropTypes.string.isRequired -}; - -export default CommentType; diff --git a/client/coral-admin/src/components/Label.css b/client/coral-admin/src/components/Label.css index fb6bd86ff..5e7f656ee 100644 --- a/client/coral-admin/src/components/Label.css +++ b/client/coral-admin/src/components/Label.css @@ -8,7 +8,6 @@ height: 24px; letter-spacing: 0.4px; line-height: 22px; - background: #063B9A; min-width: 80px; text-align: center; } @@ -20,7 +19,4 @@ margin-right: 4px; } -.isFlag { - background: #d03235; -} diff --git a/client/coral-admin/src/components/ReplyBadge.js b/client/coral-admin/src/components/ReplyBadge.js deleted file mode 100644 index 5c8bb88ba..000000000 --- a/client/coral-admin/src/components/ReplyBadge.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import {Badge} from 'coral-ui'; -import t from 'coral-framework/services/i18n'; - -const ReplyBadge = () => ( - - {t('modqueue.reply')} - -); - -export default ReplyBadge; diff --git a/client/coral-admin/src/components/UserDetailComment.css b/client/coral-admin/src/components/UserDetailComment.css index 2dfd61cf3..d1e2b007a 100644 --- a/client/coral-admin/src/components/UserDetailComment.css +++ b/client/coral-admin/src/components/UserDetailComment.css @@ -55,7 +55,7 @@ position: relative; } -.badgeBar { +.labels { position: absolute; right: 0px; } diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index fa196dc41..2aec28ab9 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -4,16 +4,14 @@ import {Link} from 'react-router'; import {Icon} from 'coral-ui'; import FlagBox from './FlagBox'; -import ReplyBadge from './ReplyBadge'; import styles from './UserDetailComment.css'; -import CommentType from './CommentType'; import {getActionSummary} from 'coral-framework/utils'; import ActionButton from 'coral-admin/src/components/ActionButton'; import CommentBodyHighlighter from 'coral-admin/src/components/CommentBodyHighlighter'; import IfHasLink from 'coral-admin/src/components/IfHasLink'; import cn from 'classnames'; -import {getCommentType} from 'coral-admin/src/utils/comment'; import CommentAnimatedEdit from './CommentAnimatedEdit'; +import CommentLabels from '../containers/CommentLabels'; import t, {timeago} from 'coral-framework/services/i18n'; @@ -35,7 +33,6 @@ class UserDetailComment extends React.Component { const flagActionSummaries = getActionSummary('FlagActionSummary', comment); const flagActions = comment.actions && comment.actions.filter((a) => a.__typename === 'FlagAction'); - const commentType = getCommentType(comment); return (
  • - {comment.hasParent && } - +
    +
  • diff --git a/client/coral-admin/src/containers/CommentLabels.js b/client/coral-admin/src/containers/CommentLabels.js new file mode 100644 index 000000000..ab9639bcd --- /dev/null +++ b/client/coral-admin/src/containers/CommentLabels.js @@ -0,0 +1,21 @@ +import {gql} from 'react-apollo'; +import CommentLabels from '../components/CommentLabels'; +import withFragments from 'coral-framework/hocs/withFragments'; + +export default withFragments({ + comment: gql` + fragment CoralAdmin_CommentLabels_comment on Comment { + hasParent + status + actions { + __typename + ... on FlagAction { + reason + } + user { + id + } + } + } + ` +})(CommentLabels); diff --git a/client/coral-admin/src/containers/UserDetailComment.js b/client/coral-admin/src/containers/UserDetailComment.js index 9f0e3a793..9f0199bce 100644 --- a/client/coral-admin/src/containers/UserDetailComment.js +++ b/client/coral-admin/src/containers/UserDetailComment.js @@ -1,6 +1,8 @@ import {gql} from 'react-apollo'; import UserDetailComment from '../components/UserDetailComment'; import withFragments from 'coral-framework/hocs/withFragments'; +import {getDefinitionName} from 'coral-framework/utils'; +import CommentLabels from './CommentLabels'; export default withFragments({ comment: gql` @@ -35,6 +37,8 @@ export default withFragments({ editing { edited } + ...${getDefinitionName(CommentLabels.fragments.comment)} } + ${CommentLabels.fragments.comment} ` })(UserDetailComment); diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js index ce055e644..b210d8f23 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -107,9 +107,7 @@ class Comment extends React.Component { }
    a.__typename === 'FlagAction')) { - commentType = 'flagged'; - } - return commentType; -} diff --git a/client/coral-ui/components/Badge.css b/client/coral-ui/components/Badge.css deleted file mode 100644 index db68ebd58..000000000 --- a/client/coral-ui/components/Badge.css +++ /dev/null @@ -1,20 +0,0 @@ -.badge { - display: inline-block; - color: white; - background: grey; - box-sizing: border-box; - padding: 2px 5px; - font-size: 12px; - height: 24px; - letter-spacing: 0.4px; - line-height: 22px; - background-color: #3D73D5; - margin-right: 4px; -} - -.icon { - font-size: 14px; - vertical-align: text-top; - margin: 0; - margin-right: 4px; -} \ No newline at end of file diff --git a/client/coral-ui/components/Badge.js b/client/coral-ui/components/Badge.js deleted file mode 100644 index c1ba8100b..000000000 --- a/client/coral-ui/components/Badge.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import styles from './Badge.css'; -import Icon from './Icon'; -import cn from 'classnames'; - -const Badge = ({className, children, icon, props}) => ( - - {icon && } - {children} - -); - -export default Badge; diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js index 8f261bcca..8a538d120 100644 --- a/client/coral-ui/index.js +++ b/client/coral-ui/index.js @@ -26,4 +26,3 @@ export {default as Option} from './components/Option'; export {default as SnackBar} from './components/SnackBar'; export {default as TextArea} from './components/TextArea'; export {default as Drawer} from './components/Drawer'; -export {default as Badge} from './components/Badge'; From 7a4aae93b420044feb4c7f708fc132595aa9c2a0 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 19 Sep 2017 23:14:34 +0700 Subject: [PATCH 04/11] Introduce adminCommentLabels and implement Toxic comment label --- .../src/components/CommentLabels.css | 14 +++++++++++ .../src/components/CommentLabels.js | 20 +++++++++------- .../src/containers/CommentLabels.js | 6 +++++ .../src => coral-ui}/components/FlagLabel.css | 0 .../src => coral-ui}/components/FlagLabel.js | 0 .../src => coral-ui}/components/Label.css | 0 .../src => coral-ui}/components/Label.js | 5 ++-- client/coral-ui/index.js | 2 ++ .../client/components/ToxicLabel.js | 8 +++++++ .../client/containers/ToxicLabel.js | 24 +++++++++++++++++++ .../client/index.js | 2 ++ 11 files changed, 70 insertions(+), 11 deletions(-) rename client/{coral-admin/src => coral-ui}/components/FlagLabel.css (100%) rename client/{coral-admin/src => coral-ui}/components/FlagLabel.js (100%) rename client/{coral-admin/src => coral-ui}/components/Label.css (100%) rename client/{coral-admin/src => coral-ui}/components/Label.js (71%) create mode 100644 plugins/talk-plugin-toxic-comments/client/components/ToxicLabel.js create mode 100644 plugins/talk-plugin-toxic-comments/client/containers/ToxicLabel.js diff --git a/client/coral-admin/src/components/CommentLabels.css b/client/coral-admin/src/components/CommentLabels.css index 718cd7dfd..b7a5699fe 100644 --- a/client/coral-admin/src/components/CommentLabels.css +++ b/client/coral-admin/src/components/CommentLabels.css @@ -1,4 +1,18 @@ .root { + display: flex; + justify-content: flex-end; +} + +.coreLabels { + > *:not(:last-child) { + margin-right: 3px; + } +} + +.slot { + &:not(:empty) { + padding-left: 3px; + } > *:not(:last-child) { margin-right: 3px; } diff --git a/client/coral-admin/src/components/CommentLabels.js b/client/coral-admin/src/components/CommentLabels.js index 333f905ef..7af7e9a7e 100644 --- a/client/coral-admin/src/components/CommentLabels.js +++ b/client/coral-admin/src/components/CommentLabels.js @@ -1,7 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Label from './Label'; -import FlagLabel from './FlagLabel'; +import Label from 'coral-ui/components/Label'; +import Slot from 'coral-framework/components/Slot'; +import FlagLabel from 'coral-ui/components/FlagLabel'; import cn from 'classnames'; import styles from './CommentLabels.css'; @@ -17,14 +18,17 @@ function hasHistoryFlag(actions) { return actions.some((action) => action.__typename === 'FlagAction' && action.reason === 'TRUST'); } -const CommentLabels = ({comment: {className, status, actions, hasParent}}) => { +const CommentLabels = ({comment, comment: {className, status, actions, hasParent}}) => { return (
    - {hasParent && } - {status === 'PREMOD' && } - {isUserFlagged(actions) && User} - {hasSuspectedWords(actions) && Suspect} - {hasHistoryFlag(actions) && History} +
    + {hasParent && } + {status === 'PREMOD' && } + {isUserFlagged(actions) && User} + {hasSuspectedWords(actions) && Suspect} + {hasHistoryFlag(actions) && History} +
    +
    ); }; diff --git a/client/coral-admin/src/containers/CommentLabels.js b/client/coral-admin/src/containers/CommentLabels.js index ab9639bcd..af6a0d963 100644 --- a/client/coral-admin/src/containers/CommentLabels.js +++ b/client/coral-admin/src/containers/CommentLabels.js @@ -1,6 +1,11 @@ import {gql} from 'react-apollo'; import CommentLabels from '../components/CommentLabels'; import withFragments from 'coral-framework/hocs/withFragments'; +import {getSlotFragmentSpreads} from 'coral-framework/utils'; + +const slots = [ + 'adminCommentLabels', +]; export default withFragments({ comment: gql` @@ -16,6 +21,7 @@ export default withFragments({ id } } + ${getSlotFragmentSpreads(slots, 'comment')} } ` })(CommentLabels); diff --git a/client/coral-admin/src/components/FlagLabel.css b/client/coral-ui/components/FlagLabel.css similarity index 100% rename from client/coral-admin/src/components/FlagLabel.css rename to client/coral-ui/components/FlagLabel.css diff --git a/client/coral-admin/src/components/FlagLabel.js b/client/coral-ui/components/FlagLabel.js similarity index 100% rename from client/coral-admin/src/components/FlagLabel.js rename to client/coral-ui/components/FlagLabel.js diff --git a/client/coral-admin/src/components/Label.css b/client/coral-ui/components/Label.css similarity index 100% rename from client/coral-admin/src/components/Label.css rename to client/coral-ui/components/Label.css diff --git a/client/coral-admin/src/components/Label.js b/client/coral-ui/components/Label.js similarity index 71% rename from client/coral-admin/src/components/Label.js rename to client/coral-ui/components/Label.js index 9d1b736de..a73b6183d 100644 --- a/client/coral-admin/src/components/Label.js +++ b/client/coral-ui/components/Label.js @@ -4,9 +4,9 @@ import styles from './Label.css'; import {Icon} from 'coral-ui'; import cn from 'classnames'; -const Label = ({iconName, children, className, isFlag}) => { +const Label = ({iconName, children, className}) => { return ( - + {children} ); @@ -14,7 +14,6 @@ const Label = ({iconName, children, className, isFlag}) => { Label.propTypes = { className: PropTypes.string, - isFlag: PropTypes.bool, children: PropTypes.node.isRequired, iconName: PropTypes.string, }; diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js index 8a538d120..93fe5af64 100644 --- a/client/coral-ui/index.js +++ b/client/coral-ui/index.js @@ -26,3 +26,5 @@ export {default as Option} from './components/Option'; export {default as SnackBar} from './components/SnackBar'; export {default as TextArea} from './components/TextArea'; export {default as Drawer} from './components/Drawer'; +export {default as Label} from './components/Label'; +export {default as FlagLabel} from './components/FlagLabel'; diff --git a/plugins/talk-plugin-toxic-comments/client/components/ToxicLabel.js b/plugins/talk-plugin-toxic-comments/client/components/ToxicLabel.js new file mode 100644 index 000000000..3c17703dd --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/client/components/ToxicLabel.js @@ -0,0 +1,8 @@ +import React from 'react'; +import {FlagLabel} from 'plugin-api/beta/client/components/ui'; + +const ToxicLabel = () => ( + Toxic +); + +export default ToxicLabel; diff --git a/plugins/talk-plugin-toxic-comments/client/containers/ToxicLabel.js b/plugins/talk-plugin-toxic-comments/client/containers/ToxicLabel.js new file mode 100644 index 000000000..0d2e9e14b --- /dev/null +++ b/plugins/talk-plugin-toxic-comments/client/containers/ToxicLabel.js @@ -0,0 +1,24 @@ +import {compose, gql} from 'react-apollo'; +import {withFragments, excludeIf} from 'plugin-api/beta/client/hocs'; +import ToxicLabel from '../components/ToxicLabel'; + +function isToxic(actions) { + return actions.some((action) => action.__typename === 'FlagAction' && action.reason === 'TOXIC_COMMENT'); +} + +const enhance = compose( + withFragments({ + comment: gql` + fragment TalkToxicComments_Comment on Comment { + actions { + __typename + ... on FlagAction { + reason + } + } + }`, + }), + excludeIf(({comment: {actions}}) => !isToxic(actions)), +); + +export default enhance(ToxicLabel); diff --git a/plugins/talk-plugin-toxic-comments/client/index.js b/plugins/talk-plugin-toxic-comments/client/index.js index bb0417606..8a16b8dea 100644 --- a/plugins/talk-plugin-toxic-comments/client/index.js +++ b/plugins/talk-plugin-toxic-comments/client/index.js @@ -1,9 +1,11 @@ import translations from './translations.yml'; import CheckToxicityHook from './containers/CheckToxicityHook'; +import ToxicLabel from './containers/ToxicLabel'; export default { translations, slots: { commentInputDetailArea: [CheckToxicityHook], + adminCommentLabels: [ToxicLabel], }, }; From e046a9652dd7b0bb18f715f0a9001be64aab7108 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 19 Sep 2017 23:52:25 +0700 Subject: [PATCH 05/11] Fix SYSTEM_WITHHELD comments missing in all queue and user detail --- .../src/routes/Moderation/queueConfig.js | 1 - graph/loaders/comments.js | 14 ++++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/client/coral-admin/src/routes/Moderation/queueConfig.js b/client/coral-admin/src/routes/Moderation/queueConfig.js index b90979efe..f17c38a09 100644 --- a/client/coral-admin/src/routes/Moderation/queueConfig.js +++ b/client/coral-admin/src/routes/Moderation/queueConfig.js @@ -28,7 +28,6 @@ export default { name: t('modqueue.rejected'), }, all: { - statuses: ['NONE', 'PREMOD', 'ACCEPTED', 'REJECTED'], icon: 'question_answer', name: t('modqueue.all'), }, diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index 386d33fd1..e17877098 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -281,12 +281,14 @@ const getCommentsByQuery = async (ctx, {ids, statuses, asset_id, parent_id, auth // Only administrators can search for comments with statuses that are not // `null`, or `'ACCEPTED'`. - if (ctx.user != null && ctx.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS) && statuses && statuses.length > 0) { - comments = comments.where({ - status: { - $in: statuses - } - }); + if (ctx.user != null && ctx.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS)) { + if (statuses && statuses.length > 0) { + comments = comments.where({ + status: { + $in: statuses + } + }); + } } else { comments = comments.where({ status: { From b482976c582f82bd0223229653b4e2766d562f01 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 20 Sep 2017 02:39:31 +0700 Subject: [PATCH 06/11] Allow querying for all statuses --- .../Moderation/containers/Moderation.js | 16 +++++++----- graph/loaders/comments.js | 26 ++++++++++++------- graph/resolvers/asset.js | 1 + graph/resolvers/comment.js | 1 + graph/typeDefs.graphql | 15 +++++++---- 5 files changed, 37 insertions(+), 22 deletions(-) diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index 2ff6b6732..baf6b8dfd 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -170,7 +170,8 @@ class ModerationContainer extends Component { cursor: this.props.root[tab].endCursor, sortOrder: this.props.data.variables.sortOrder, asset_id: this.props.data.variables.asset_id, - statuses: this.props.queueConfig[tab].statuses, + statuses: this.props.queueConfig[tab].statuses || null, + tags: this.props.queueConfig[tab].tags || null, action_type: this.props.queueConfig[tab].action_type, }; return this.props.data.fetchMore({ @@ -214,7 +215,7 @@ class ModerationContainer extends Component { return ; } - const premodEnabled = assetId ? isPremod(asset.settings.moderation) : + const premodEnabled = assetId ? isPremod(asset.settings.moderation) : isPremod(settings.moderation); const currentQueueConfig = Object.assign({}, this.props.queueConfig); @@ -293,8 +294,8 @@ const COMMENT_REJECTED_SUBSCRIPTION = gql` `; const LOAD_MORE_QUERY = gql` - query CoralAdmin_Moderation_LoadMore($limit: Int = 10, $cursor: Cursor, $sortOrder: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) { - comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sortOrder: $sortOrder, action_type: $action_type}) { + query CoralAdmin_Moderation_LoadMore($limit: Int = 10, $cursor: Cursor, $sortOrder: SORT_ORDER, $asset_id: ID, $tags:[String!], $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) { + comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sortOrder: $sortOrder, action_type: $action_type, tags: $tags}) { nodes { ...${getDefinitionName(Comment.fragments.comment)} } @@ -319,10 +320,10 @@ const commentConnectionFragment = gql` `; const withModQueueQuery = withQuery(({queueConfig}) => gql` - query CoralAdmin_Moderation($asset_id: ID, $sortOrder: SORT_ORDER, $allAssets: Boolean!) { + query CoralAdmin_Moderation($asset_id: ID, $sortOrder: SORT_ORDER, $allAssets: Boolean!, $nullStatuses: [COMMENT_STATUS!]) { ${Object.keys(queueConfig).map((queue) => ` ${queue}: comments(query: { - ${queueConfig[queue].statuses ? `statuses: [${queueConfig[queue].statuses.join(', ')}],` : ''} + statuses: ${queueConfig[queue].statuses ? `[${queueConfig[queue].statuses.join(', ')}],` : '$nullStatuses'} ${queueConfig[queue].tags ? `tags: ["${queueConfig[queue].tags.join('", "')}"],` : ''} ${queueConfig[queue].action_type ? `action_type: ${queueConfig[queue].action_type}` : ''} asset_id: $asset_id, @@ -333,7 +334,7 @@ const withModQueueQuery = withQuery(({queueConfig}) => gql` `)} ${Object.keys(queueConfig).map((queue) => ` ${queue}Count: commentCount(query: { - ${queueConfig[queue].statuses ? `statuses: [${queueConfig[queue].statuses.join(', ')}],` : ''} + statuses: ${queueConfig[queue].statuses ? `[${queueConfig[queue].statuses.join(', ')}],` : '$nullStatuses'} ${queueConfig[queue].tags ? `tags: ["${queueConfig[queue].tags.join('", "')}"],` : ''} ${queueConfig[queue].action_type ? `action_type: ${queueConfig[queue].action_type}` : ''} asset_id: $asset_id, @@ -361,6 +362,7 @@ const withModQueueQuery = withQuery(({queueConfig}) => gql` asset_id: id, sortOrder: props.moderation.sortOrder, allAssets: id === null, + nullStatuses: null, }, fetchPolicy: 'network-only' }; diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index e17877098..adfa71eb4 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -91,12 +91,19 @@ const getParentCountsByAssetID = (context, asset_ids) => { const getCommentCountByQuery = (context, {ids, statuses, asset_id, parent_id, author_id, tags, action_type}) => { let query = CommentModel.find(); - if (ids) { - query = query.where({id: {$in: ids}}); + if ( + (context.user != null && context.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS)) || + statuses && statuses.every((status) => ['NONE', 'ACCEPTED'].includes(status)) + ) { + if (statuses) { + query = query.where({status: {$in: statuses}}); + } + } else { + return null; } - if (statuses) { - query = query.where({status: {$in: statuses}}); + if (ids) { + query = query.where({id: {$in: ids}}); } if (asset_id != null) { @@ -281,7 +288,10 @@ const getCommentsByQuery = async (ctx, {ids, statuses, asset_id, parent_id, auth // Only administrators can search for comments with statuses that are not // `null`, or `'ACCEPTED'`. - if (ctx.user != null && ctx.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS)) { + if ( + (ctx.user != null && ctx.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS)) || + statuses && statuses.every((status) => ['NONE', 'ACCEPTED'].includes(status)) + ) { if (statuses && statuses.length > 0) { comments = comments.where({ status: { @@ -290,11 +300,7 @@ const getCommentsByQuery = async (ctx, {ids, statuses, asset_id, parent_id, auth }); } } else { - comments = comments.where({ - status: { - $in: ['NONE', 'ACCEPTED'] - } - }); + return null; } if (ctx.user != null && ctx.user.can(SEARCH_OTHERS_COMMENTS) && action_type) { diff --git a/graph/resolvers/asset.js b/graph/resolvers/asset.js index b5fd92d34..2680f9f76 100644 --- a/graph/resolvers/asset.js +++ b/graph/resolvers/asset.js @@ -23,6 +23,7 @@ const Asset = { // Include the asset id in the search. query.asset_id = id; + query.statuses = ['NONE', 'ACCEPTED']; return Comments.getByQuery(query); }, diff --git a/graph/resolvers/comment.js b/graph/resolvers/comment.js index cd1e93141..8e063ad3c 100644 --- a/graph/resolvers/comment.js +++ b/graph/resolvers/comment.js @@ -26,6 +26,7 @@ const Comment = { query.asset_id = asset_id; query.parent_id = id; + query.statuses = ['NONE', 'ACCEPTED']; return Comments.getByQuery(query); }, diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index f36874639..5b3fe7ef9 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -264,8 +264,9 @@ input CommentsQuery { # Author of the comments author_id: ID - # Current status of a comment. Requires the `ADMIN` role. - statuses: [COMMENT_STATUS!] + # Current status of a comment. + # This field is restricted. + statuses: [COMMENT_STATUS!] = [NONE, ACCEPTED] # Asset that a comment is on. asset_id: ID @@ -317,8 +318,9 @@ input RepliesQuery { # methods. input CommentCountQuery { - # Current status of a comment. Requires the `ADMIN` role. - statuses: [COMMENT_STATUS!] + # Current status of a comment. + # This field is restricted. + statuses: [COMMENT_STATUS!] = [NONE, ACCEPTED] # Asset that a comment is on. asset_id: ID @@ -688,7 +690,7 @@ type Asset { # The comments that are attached to the asset. When `deep` is true, the # comments returned will be at all depths. - comments(query: CommentsQuery = {}, deep: Boolean = false): CommentConnection! + comments(query: CommentsQuery = {}, deep: Boolean = false): CommentConnection # A Comment from the Asset by comment's ID comment(id: ID!): Comment @@ -875,6 +877,9 @@ type CreateCommentResponse implements Response { # The comment that was created. comment: Comment + # Flags that was assigned during creation of the comment. + flags: [FlagAction] + # An array of errors relating to the mutation that occurred. errors: [UserError!] } From 5f7c4f82e72ed89cc83e8a1c2f8f13ba4400b6ac Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 20 Sep 2017 02:50:43 +0700 Subject: [PATCH 07/11] Return flags when creating comment, restrict access to actions in comment resolver --- client/coral-embed-stream/src/graphql/index.js | 14 +++++--------- client/talk-plugin-commentbox/CommentBox.js | 11 ++++++----- graph/resolvers/root_mutation.js | 12 +++++++++--- .../client/components/CheckToxicityHook.js | 4 ++-- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/client/coral-embed-stream/src/graphql/index.js b/client/coral-embed-stream/src/graphql/index.js index 82d6ca9e3..d7af76d82 100644 --- a/client/coral-embed-stream/src/graphql/index.js +++ b/client/coral-embed-stream/src/graphql/index.js @@ -64,6 +64,10 @@ export default { hasNextPage } } + flags { + reason + message + } } fragment CoralEmbedStream_CreateCommentResponse_Comment on Comment { @@ -77,14 +81,6 @@ export default { title url } - actions { - __typename - id - ... on FlagAction { - reason - message - } - } tags { tag { name @@ -124,6 +120,7 @@ export default { createComment: { __typename: 'CreateCommentResponse', errors: null, + flags: [], comment: { __typename: 'Comment', user: { @@ -133,7 +130,6 @@ export default { }, created_at: new Date().toISOString(), body, - actions: [], action_summaries: [], tags: tags.map((tag) => ({ tag: { diff --git a/client/talk-plugin-commentbox/CommentBox.js b/client/talk-plugin-commentbox/CommentBox.js index 409ad86ec..49c4df0eb 100644 --- a/client/talk-plugin-commentbox/CommentBox.js +++ b/client/talk-plugin-commentbox/CommentBox.js @@ -13,18 +13,18 @@ export const name = 'talk-plugin-commentbox'; const notifyReasons = ['LINKS', 'TRUST']; -function shouldNotify(actions = []) { - return actions.some(({reason}) => notifyReasons.includes(reason)); +function shouldNotify(flags = []) { + return flags.some(({reason}) => notifyReasons.includes(reason)); } // Given a newly posted comment's status, show a notification to the user // if needed -export const notifyForNewCommentStatus = (notify, comment) => { +export const notifyForNewCommentStatus = (notify, comment, flags) => { if (comment.status === 'REJECTED') { notify('error', t('comment_box.comment_post_banned_word')); } else if ( comment.status === 'PREMOD' || - comment.status === 'SYSTEM_WITHHELD' && shouldNotify(comment.actions)) { + comment.status === 'SYSTEM_WITHHELD' && shouldNotify(flags)) { notify('success', t('comment_box.comment_post_notif_premod')); } }; @@ -78,11 +78,12 @@ class CommentBox extends React.Component { .then(({data}) => { this.setState({loadingState: 'success', body: ''}); const postedComment = data.createComment.comment; + const flags = data.createComment.flags; // Execute postSubmit Hooks this.state.hooks.postSubmit.forEach((hook) => hook(data)); - notifyForNewCommentStatus(notify, postedComment); + notifyForNewCommentStatus(notify, postedComment, flags); if (commentPostedHandler) { commentPostedHandler(); diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index df67b8e8a..c7794303b 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -1,7 +1,13 @@ const RootMutation = { - createComment: async (_, {input}, {mutators: {Comment}}) => ({ - comment: await Comment.create(input), - }), + createComment: async (_, {input}, {mutators: {Comment}, loaders: {Actions}}) => { + const comment = await Comment.create(input); + + // Retrieve flags that was assigned to comment. + const actions = await Actions.getByID.load(comment.id); + const flags = actions.filter(({action_type}) => action_type === 'FLAG'); + + return {comment, flags}; + }, editComment: async (_, {id, asset_id, edit: {body}}, {mutators: {Comment}}) => ({ comment: await Comment.edit({id, asset_id, edit: {body}}), }), diff --git a/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js index 8572bdd5a..44e2e89a7 100644 --- a/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js +++ b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js @@ -23,8 +23,8 @@ export default class CheckToxicityHook extends React.Component { }); this.toxicityPostHook = this.props.registerHook('postSubmit', (result) => { - const actions = result.createComment.comment && result.createComment.comment.actions; - if (actions && actions.some(({reason}) => reason === 'TOXIC_COMMENT')) { + const flags = result.createComment.flags; + if (flags && flags.some(({reason}) => reason === 'TOXIC_COMMENT')) { this.props.notify('error', t('talk-plugin-toxic-comments.still_toxic')); } From 42954fb84ab4b9e7651e66eeafe0da76a369fa07 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 20 Sep 2017 04:17:15 +0700 Subject: [PATCH 08/11] Return all actions in CreateCommentResponse --- client/coral-embed-stream/src/graphql/index.js | 11 +++++++---- client/talk-plugin-commentbox/CommentBox.js | 12 ++++++------ graph/resolvers/root_mutation.js | 5 ++--- graph/typeDefs.graphql | 6 +++--- .../client/components/CheckToxicityHook.js | 4 ++-- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/client/coral-embed-stream/src/graphql/index.js b/client/coral-embed-stream/src/graphql/index.js index d7af76d82..20ed8a1d6 100644 --- a/client/coral-embed-stream/src/graphql/index.js +++ b/client/coral-embed-stream/src/graphql/index.js @@ -64,9 +64,12 @@ export default { hasNextPage } } - flags { - reason - message + actions { + __typename + ... on FlagAction { + reason + message + } } } @@ -120,7 +123,7 @@ export default { createComment: { __typename: 'CreateCommentResponse', errors: null, - flags: [], + actions: [], comment: { __typename: 'Comment', user: { diff --git a/client/talk-plugin-commentbox/CommentBox.js b/client/talk-plugin-commentbox/CommentBox.js index 49c4df0eb..b210f4e79 100644 --- a/client/talk-plugin-commentbox/CommentBox.js +++ b/client/talk-plugin-commentbox/CommentBox.js @@ -13,18 +13,18 @@ export const name = 'talk-plugin-commentbox'; const notifyReasons = ['LINKS', 'TRUST']; -function shouldNotify(flags = []) { - return flags.some(({reason}) => notifyReasons.includes(reason)); +function shouldNotify(actions = []) { + return actions.some(({__typename, reason}) => __typename === 'FlagAction' && notifyReasons.includes(reason)); } // Given a newly posted comment's status, show a notification to the user // if needed -export const notifyForNewCommentStatus = (notify, comment, flags) => { +export const notifyForNewCommentStatus = (notify, comment, actions) => { if (comment.status === 'REJECTED') { notify('error', t('comment_box.comment_post_banned_word')); } else if ( comment.status === 'PREMOD' || - comment.status === 'SYSTEM_WITHHELD' && shouldNotify(flags)) { + comment.status === 'SYSTEM_WITHHELD' && shouldNotify(actions)) { notify('success', t('comment_box.comment_post_notif_premod')); } }; @@ -78,12 +78,12 @@ class CommentBox extends React.Component { .then(({data}) => { this.setState({loadingState: 'success', body: ''}); const postedComment = data.createComment.comment; - const flags = data.createComment.flags; + const actions = data.createComment.actions; // Execute postSubmit Hooks this.state.hooks.postSubmit.forEach((hook) => hook(data)); - notifyForNewCommentStatus(notify, postedComment, flags); + notifyForNewCommentStatus(notify, postedComment, actions); if (commentPostedHandler) { commentPostedHandler(); diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index c7794303b..17235a59e 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -2,11 +2,10 @@ const RootMutation = { createComment: async (_, {input}, {mutators: {Comment}, loaders: {Actions}}) => { const comment = await Comment.create(input); - // Retrieve flags that was assigned to comment. + // Retrieve actions that was assigned to comment. const actions = await Actions.getByID.load(comment.id); - const flags = actions.filter(({action_type}) => action_type === 'FLAG'); - return {comment, flags}; + return {comment, actions}; }, editComment: async (_, {id, asset_id, edit: {body}}, {mutators: {Comment}}) => ({ comment: await Comment.edit({id, asset_id, edit: {body}}), diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 5b3fe7ef9..71928f49e 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -261,7 +261,7 @@ enum ACTION_TYPE { # CommentsQuery allows the ability to query comments by a specific methods. input CommentsQuery { - # Author of the comments + # Author of the commente author_id: ID # Current status of a comment. @@ -877,8 +877,8 @@ type CreateCommentResponse implements Response { # The comment that was created. comment: Comment - # Flags that was assigned during creation of the comment. - flags: [FlagAction] + # Actions that was assigned during creation of the comment. + actions: [Action] # An array of errors relating to the mutation that occurred. errors: [UserError!] diff --git a/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js index 44e2e89a7..e4bd68097 100644 --- a/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js +++ b/plugins/talk-plugin-toxic-comments/client/components/CheckToxicityHook.js @@ -23,8 +23,8 @@ export default class CheckToxicityHook extends React.Component { }); this.toxicityPostHook = this.props.registerHook('postSubmit', (result) => { - const flags = result.createComment.flags; - if (flags && flags.some(({reason}) => reason === 'TOXIC_COMMENT')) { + const actions = result.createComment.actions; + if (actions && actions.some(({__typename, reason}) => __typename === 'FlagAction' && reason === 'TOXIC_COMMENT')) { this.props.notify('error', t('talk-plugin-toxic-comments.still_toxic')); } From c3e24a045131a09a15ae5f396ddf5cd960feeae1 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 20 Sep 2017 04:29:03 +0700 Subject: [PATCH 09/11] Apply suggenstions --- graph/loaders/comments.js | 34 ++++++++++++++++------------------ graph/mutators/comment.js | 2 +- graph/resolvers/asset.js | 1 - 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index adfa71eb4..2eb0811a6 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -91,17 +91,19 @@ const getParentCountsByAssetID = (context, asset_ids) => { const getCommentCountByQuery = (context, {ids, statuses, asset_id, parent_id, author_id, tags, action_type}) => { let query = CommentModel.find(); + // If user queries for statuses other than NONE and/or ACCEPTED statuses, it needs + // special priviledges. if ( - (context.user != null && context.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS)) || - statuses && statuses.every((status) => ['NONE', 'ACCEPTED'].includes(status)) + statuses && statuses.some((status) => !['NONE', 'ACCEPTED'].includes(status)) && + (context.user == null || !context.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS)) ) { - if (statuses) { - query = query.where({status: {$in: statuses}}); - } - } else { return null; } + if (statuses) { + query = query.where({status: {$in: statuses}}); + } + if (ids) { query = query.where({id: {$in: ids}}); } @@ -286,23 +288,19 @@ const executeWithSort = async (ctx, query, {cursor, sortOrder, sortBy, limit}) = const getCommentsByQuery = async (ctx, {ids, statuses, asset_id, parent_id, author_id, limit, cursor, sortOrder, sortBy, excludeIgnored, tags, action_type}) => { let comments = CommentModel.find(); - // Only administrators can search for comments with statuses that are not - // `null`, or `'ACCEPTED'`. + // If user queries for statuses other than NONE and/or ACCEPTED statuses, it needs + // special priviledges. if ( - (ctx.user != null && ctx.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS)) || - statuses && statuses.every((status) => ['NONE', 'ACCEPTED'].includes(status)) + statuses && statuses.some((status) => !['NONE', 'ACCEPTED'].includes(status)) && + (ctx.user == null || !ctx.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS)) ) { - if (statuses && statuses.length > 0) { - comments = comments.where({ - status: { - $in: statuses - } - }); - } - } else { return null; } + if (statuses) { + comments = comments.where({status: {$in: statuses}}); + } + if (ctx.user != null && ctx.user.can(SEARCH_OTHERS_COMMENTS) && action_type) { comments = comments.where({ [`action_counts.${sc(action_type.toLowerCase())}`]: { diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 2aee766a5..a1315d847 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -398,7 +398,7 @@ const moderationPhases = [ * @param {String} body body of the comment * @param {String} [asset_id] asset for the comment * @param {Object} [wordlist={}] the results of the wordlist scan - * @return {Object} resolves to the comment's status and actions + * @return {Promise} resolves to the comment's status and actions */ const resolveCommentModeration = async (context, comment) => { diff --git a/graph/resolvers/asset.js b/graph/resolvers/asset.js index 2680f9f76..b5fd92d34 100644 --- a/graph/resolvers/asset.js +++ b/graph/resolvers/asset.js @@ -23,7 +23,6 @@ const Asset = { // Include the asset id in the search. query.asset_id = id; - query.statuses = ['NONE', 'ACCEPTED']; return Comments.getByQuery(query); }, From 2d96163b76d4f5fb014e75711443dce8ed956344 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 20 Sep 2017 04:35:14 +0700 Subject: [PATCH 10/11] Disallow querying for all comments unpriviledgly --- graph/loaders/comments.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index 2eb0811a6..d79b5dc74 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -94,7 +94,7 @@ const getCommentCountByQuery = (context, {ids, statuses, asset_id, parent_id, au // If user queries for statuses other than NONE and/or ACCEPTED statuses, it needs // special priviledges. if ( - statuses && statuses.some((status) => !['NONE', 'ACCEPTED'].includes(status)) && + !statuses || statuses.some((status) => !['NONE', 'ACCEPTED'].includes(status)) && (context.user == null || !context.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS)) ) { return null; @@ -291,7 +291,7 @@ const getCommentsByQuery = async (ctx, {ids, statuses, asset_id, parent_id, auth // If user queries for statuses other than NONE and/or ACCEPTED statuses, it needs // special priviledges. if ( - statuses && statuses.some((status) => !['NONE', 'ACCEPTED'].includes(status)) && + !statuses || statuses.some((status) => !['NONE', 'ACCEPTED'].includes(status)) && (ctx.user == null || !ctx.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS)) ) { return null; From 961111c3f7f26fdda4e89cb00023833dcd6b3b71 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 20 Sep 2017 04:36:58 +0700 Subject: [PATCH 11/11] Allow priviledged user to query for all statuses --- graph/loaders/comments.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index d79b5dc74..b6a347b8e 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -94,7 +94,7 @@ const getCommentCountByQuery = (context, {ids, statuses, asset_id, parent_id, au // If user queries for statuses other than NONE and/or ACCEPTED statuses, it needs // special priviledges. if ( - !statuses || statuses.some((status) => !['NONE', 'ACCEPTED'].includes(status)) && + (!statuses || statuses.some((status) => !['NONE', 'ACCEPTED'].includes(status))) && (context.user == null || !context.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS)) ) { return null; @@ -291,7 +291,7 @@ const getCommentsByQuery = async (ctx, {ids, statuses, asset_id, parent_id, auth // If user queries for statuses other than NONE and/or ACCEPTED statuses, it needs // special priviledges. if ( - !statuses || statuses.some((status) => !['NONE', 'ACCEPTED'].includes(status)) && + (!statuses || statuses.some((status) => !['NONE', 'ACCEPTED'].includes(status))) && (ctx.user == null || !ctx.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS)) ) { return null;