From 5a71db25686872fb04b8c47acd847a8c001f3dab Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 14 Nov 2017 13:58:18 +0100 Subject: [PATCH 1/2] Undo to previous status + Live updates --- .../src/containers/SuspendUserDialog.js | 2 +- .../Moderation/containers/Moderation.js | 28 +++++++++++++++++++ .../src/components/Comment.js | 5 +++- .../src/containers/Comment.js | 3 ++ .../coral-embed-stream/src/graphql/index.js | 4 +++ client/coral-framework/graphql/mutations.js | 5 ++++ graph/resolvers/root_mutation.js | 6 ++-- graph/resolvers/subscription.js | 3 ++ graph/setupFunctions.js | 11 ++++++++ graph/typeDefs.graphql | 4 +++ locales/en.yml | 1 + perms/constants.js | 1 + perms/subscriptionReducer.js | 2 ++ .../client/components/BanUserDialog.js | 4 +-- 14 files changed, 71 insertions(+), 8 deletions(-) diff --git a/client/coral-admin/src/containers/SuspendUserDialog.js b/client/coral-admin/src/containers/SuspendUserDialog.js index 3d5283df9..5c554e70c 100644 --- a/client/coral-admin/src/containers/SuspendUserDialog.js +++ b/client/coral-admin/src/containers/SuspendUserDialog.js @@ -48,7 +48,7 @@ class SuspendUserDialogContainer extends Component { SuspendUserDialogContainer.propTypes = { open: PropTypes.bool, hideSuspendUserDialog: PropTypes.func, - username: PropTypes.object, + username: PropTypes.string, }; const withOrganizationName = withQuery(gql` diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index 200e5f6ae..1b75d23cc 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -111,6 +111,17 @@ class ModerationContainer extends Component { return this.handleCommentChange(prev, comment, notifyText); }, }, + { + document: COMMENT_RESET_SUBSCRIPTION, + variables, + updateQuery: (prev, {subscriptionData: {data: {commentReset: comment}}}) => { + const user = comment.status_history[comment.status_history.length - 1].assigned_by; + const notifyText = this.props.auth.user.id === user.id + ? '' + : t('modqueue.notify_reset', user.username, prepareNotificationText(comment.body)); + return this.handleCommentChange(prev, comment, notifyText); + }, + }, { document: COMMENT_EDITED_SUBSCRIPTION, variables, @@ -299,6 +310,23 @@ const COMMENT_REJECTED_SUBSCRIPTION = gql` ${Comment.fragments.comment} `; +const COMMENT_RESET_SUBSCRIPTION = gql` + subscription CommentReset($asset_id: ID){ + commentReset(asset_id: $asset_id){ + ...${getDefinitionName(Comment.fragments.comment)} + status_history { + type + created_at + assigned_by { + id + username + } + } + } + } + ${Comment.fragments.comment} +`; + const LOAD_MORE_QUERY = gql` 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}) { diff --git a/client/coral-embed-stream/src/components/Comment.js b/client/coral-embed-stream/src/components/Comment.js index 1f72a2738..7ae36b5fa 100644 --- a/client/coral-embed-stream/src/components/Comment.js +++ b/client/coral-embed-stream/src/components/Comment.js @@ -345,7 +345,10 @@ export default class Comment extends React.Component { if (!highlighted && this.commentIsRejected(comment)) { return { - this.props.setCommentStatus({commentId: comment.id, status: 'NONE'}); + this.props.setCommentStatus({ + commentId: comment.id, + status: comment.status_history[comment.status_history.length - 2].type, + }); }}/>; } diff --git a/client/coral-embed-stream/src/containers/Comment.js b/client/coral-embed-stream/src/containers/Comment.js index 1bdca0e1e..26fc872d5 100644 --- a/client/coral-embed-stream/src/containers/Comment.js +++ b/client/coral-embed-stream/src/containers/Comment.js @@ -76,6 +76,9 @@ const singleCommentFragment = gql` id username } + status_history { + type + } action_summaries { __typename count diff --git a/client/coral-embed-stream/src/graphql/index.js b/client/coral-embed-stream/src/graphql/index.js index 3f6fd4832..d18cdc435 100644 --- a/client/coral-embed-stream/src/graphql/index.js +++ b/client/coral-embed-stream/src/graphql/index.js @@ -102,6 +102,9 @@ export default { } } } + status_history { + type + } action_summaries { count current_user { @@ -182,6 +185,7 @@ export default { editableUntil: new Date().toISOString(), edited: false, }, + status_history: [], id: `pending-${uuid()}`, } } diff --git a/client/coral-framework/graphql/mutations.js b/client/coral-framework/graphql/mutations.js index 447d297ab..3a543a738 100644 --- a/client/coral-framework/graphql/mutations.js +++ b/client/coral-framework/graphql/mutations.js @@ -138,6 +138,9 @@ export const withSetCommentStatus = withMutation( const fragment = gql` fragment Talk_SetCommentStatus on Comment { status + status_history { + type + } }`; const fragmentId = `Comment_${commentId}`; @@ -145,6 +148,8 @@ export const withSetCommentStatus = withMutation( const data = proxy.readFragment({fragment, id: fragmentId}); data.status = status; + data.status_history = data.status_history ? data.status_history : []; + data.status_history.push({__typename: 'CommentStatusHistory', type: status}); proxy.writeFragment({fragment, id: fragmentId, data}); } diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index c3bf6b7e9..f2a577d46 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -43,13 +43,11 @@ const RootMutation = { setCommentStatus: async (_, {id, status}, {mutators: {Comment}, pubsub}) => { const comment = await Comment.setStatus({id, status}); if (status === 'ACCEPTED') { - - // Publish the comment status change via the subscription. pubsub.publish('commentAccepted', comment); } else if (status === 'REJECTED') { - - // Publish the comment status change via the subscription. pubsub.publish('commentRejected', comment); + } else if (status === 'NONE') { + pubsub.publish('commentReset', comment); } }, addTag: async (_, {tag}, {mutators: {Tag}}) => { diff --git a/graph/resolvers/subscription.js b/graph/resolvers/subscription.js index f6da9fb54..8be3236c1 100644 --- a/graph/resolvers/subscription.js +++ b/graph/resolvers/subscription.js @@ -11,6 +11,9 @@ const Subscription = { commentRejected(comment) { return comment; }, + commentReset(comment) { + return comment; + }, commentFlagged(comment) { return comment; }, diff --git a/graph/setupFunctions.js b/graph/setupFunctions.js index 42dc15a9f..390442f73 100644 --- a/graph/setupFunctions.js +++ b/graph/setupFunctions.js @@ -2,6 +2,7 @@ const { SUBSCRIBE_COMMENT_ACCEPTED, SUBSCRIBE_COMMENT_REJECTED, SUBSCRIBE_COMMENT_FLAGGED, + SUBSCRIBE_COMMENT_RESET, SUBSCRIBE_ALL_COMMENT_EDITED, SUBSCRIBE_ALL_COMMENT_ADDED, SUBSCRIBE_ALL_USER_SUSPENDED, @@ -93,6 +94,16 @@ const setupFunctions = plugins.get('server', 'setupFunctions').reduce((acc, {plu } }, }), + commentReset: (options, args) => ({ + commentReset: { + filter: (comment, context) => { + if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_RESET)) { + return false; + } + return !args.asset_id || comment.asset_id === args.asset_id; + } + }, + }), userSuspended: (options, args) => ({ userSuspended: { filter: (user, context) => { diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 3fbc1cec3..44b906e24 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -1376,6 +1376,10 @@ type Subscription { # Requires the `ADMIN` or `MODERATOR` role. commentRejected(asset_id: ID): Comment + # Get an update whenever the status of a comment has been reset. + # Requires the `ADMIN` or `MODERATOR` role. + commentReset(asset_id: ID): Comment + # Get an update whenever a user has been suspended. # `user_id` must match id of current user except for # users with the `ADMIN` or `MODERATOR` role. diff --git a/locales/en.yml b/locales/en.yml index eb1c885bf..22fa11a62 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -263,6 +263,7 @@ en: notify_accepted: '{0} accepted comment "{1}"' notify_rejected: '{0} rejected comment "{1}"' notify_flagged: '{0} flagged comment "{1}"' + notify_reset: '{0} reset status of comment "{1}"' approve: "Approve" approved: "Approved" ban_user: "Ban" diff --git a/perms/constants.js b/perms/constants.js index 4c85f9c26..9cda7442b 100644 --- a/perms/constants.js +++ b/perms/constants.js @@ -36,6 +36,7 @@ module.exports = { SUBSCRIBE_COMMENT_ACCEPTED: 'SUBSCRIBE_COMMENT_ACCEPTED', SUBSCRIBE_COMMENT_REJECTED: 'SUBSCRIBE_COMMENT_REJECTED', SUBSCRIBE_COMMENT_FLAGGED: 'SUBSCRIBE_COMMENT_FLAGGED', + SUBSCRIBE_COMMENT_RESET: 'SUBSCRIBE_COMMENT_RESET', SUBSCRIBE_ALL_COMMENT_ADDED: 'SUBSCRIBE_ALL_COMMENT_ADDED', SUBSCRIBE_ALL_COMMENT_EDITED: 'SUBSCRIBE_ALL_COMMENT_EDITED', SUBSCRIBE_ALL_USER_SUSPENDED: 'SUBSCRIBE_ALL_USER_SUSPENDED', diff --git a/perms/subscriptionReducer.js b/perms/subscriptionReducer.js index e0f73526a..9ce0bc016 100644 --- a/perms/subscriptionReducer.js +++ b/perms/subscriptionReducer.js @@ -9,6 +9,8 @@ module.exports = (user, perm) => { return check(user, ['ADMIN', 'MODERATOR']); case types.SUBSCRIBE_COMMENT_REJECTED: return check(user, ['ADMIN', 'MODERATOR']); + case types.SUBSCRIBE_COMMENT_RESET: + return check(user, ['ADMIN', 'MODERATOR']); case types.SUBSCRIBE_ALL_COMMENT_EDITED: return check(user, ['ADMIN', 'MODERATOR']); case types.SUBSCRIBE_ALL_COMMENT_ADDED: diff --git a/plugins/talk-plugin-moderation-actions/client/components/BanUserDialog.js b/plugins/talk-plugin-moderation-actions/client/components/BanUserDialog.js index c98e3e7c2..9db509869 100644 --- a/plugins/talk-plugin-moderation-actions/client/components/BanUserDialog.js +++ b/plugins/talk-plugin-moderation-actions/client/components/BanUserDialog.js @@ -33,9 +33,9 @@ const BanUserDialog = ({showBanDialog, closeBanDialog, banUser}) => ( ); BanUserDialog.propTypes = { - showBanDialog: PropTypes.func.isRequired, + showBanDialog: PropTypes.bool.isRequired, closeBanDialog: PropTypes.func.isRequired, banUser: PropTypes.func.isRequired, }; -export default BanUserDialog; \ No newline at end of file +export default BanUserDialog; From 5073e60df52a6db965e6fc2246ac518574927e2d Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 14 Nov 2017 14:05:30 +0100 Subject: [PATCH 2/2] Translate --- client/coral-embed-stream/src/components/CommentTombstone.js | 2 +- locales/en.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/components/CommentTombstone.js b/client/coral-embed-stream/src/components/CommentTombstone.js index 4fb8eaaf6..4347d4277 100644 --- a/client/coral-embed-stream/src/components/CommentTombstone.js +++ b/client/coral-embed-stream/src/components/CommentTombstone.js @@ -25,7 +25,7 @@ class CommentTombstone extends React.Component {

{this.getCopy()} {this.props.action === 'reject' && - Undo + {t('comment.undo_reject')} }

diff --git a/locales/en.yml b/locales/en.yml index 22fa11a62..8c3f60a2d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -17,6 +17,7 @@ en: characters_remaining: "characters remaining" comment: anon: "Anonymous" + undo_reject: "Undo" ban_user: "Ban User" comment: "Post a comment" edited: Edited