diff --git a/client/coral-admin/src/graphql/utils.js b/client/coral-admin/src/graphql/utils.js index 0b7384622..8d4418806 100644 --- a/client/coral-admin/src/graphql/utils.js +++ b/client/coral-admin/src/graphql/utils.js @@ -14,10 +14,6 @@ const ascending = (a, b) => { const descending = (a, b) => -ascending(a, b); -function truncate(s, length = 10) { - return (s.length > length) ? `${s.substring(0, length)}...` : s; -} - function queueHasComment(root, queue, id) { return root[queue].nodes.find((c) => c.id === id); } @@ -88,22 +84,16 @@ function getCommentQueues(comment) { return queues; } -function showNotification(queue, comment, user) { - const text = `${user.username} ${comment.status.toLowerCase()} comment "${truncate(comment.body, 50)}"`; - notification.info(text); -} - -export function handleCommentStatusChange(root, comment, {sort, notify, activeQueue}) { +export function handleCommentStatusChange(root, comment, {sort, notify}) { let next = root; const nextQueues = getCommentQueues(comment); let notificationShown = false; - const showNotificationOnce = (...args) => { + const showNotificationOnce = () => { if (notificationShown) { return; } - const user = comment.status_history[comment.status_history.length - 1].assigned_by; - showNotification(...[...args, user]); + notification.info(notify.text); notificationShown = true; }; @@ -111,38 +101,25 @@ export function handleCommentStatusChange(root, comment, {sort, notify, activeQu if (nextQueues.indexOf(queue) >= 0) { if (!queueHasComment(next, queue, comment.id)) { next = addCommentToQueue(next, queue, comment, sort); - if (notify && activeQueue === queue && shouldCommentBeAdded(next, queue, comment, sort)) { - showNotificationOnce(queue, comment); + if (notify && notify.activeQueue === queue && shouldCommentBeAdded(next, queue, comment, sort)) { + showNotificationOnce(comment); } } } else if(queueHasComment(next, queue, comment.id)){ next = removeCommentFromQueue(next, queue, comment.id); - if (notify && activeQueue === queue) { - showNotificationOnce(queue, comment); + if (notify && notify.activeQueue === queue) { + showNotificationOnce(comment); } } if ( - queue === 'all' + notify + && (queue === 'all' || notify.anyQueue) && queueHasComment(next, queue, comment.id) - && notify - && activeQueue === queue + && notify.activeQueue === queue ) { - showNotificationOnce(queue, comment); + showNotificationOnce(comment); } - - // TODO: Flagged notification }); return next; } - -export function handleCommentEdit(root, comment, {sort, activeQueue}) { - if ( - queueHasComment(root, activeQueue, comment.id) - || comment.status.toLowerCase() === activeQueue && shouldCommentBeAdded(root, activeQueue, comment, sort) - ) { - const text = `${comment.user.username} edited comment to "${truncate(comment.body, 50)}"`; - notification.info(text); - } - return handleCommentStatusChange(root, comment, {sort, activeQueue}); -} diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index ede004c87..e1bd18435 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -8,9 +8,10 @@ import {getDefinitionName} from 'coral-framework/utils'; import * as notification from 'coral-admin/src/services/notification'; import t, {timeago} from 'coral-framework/services/i18n'; import update from 'immutability-helper'; +import truncate from 'lodash/truncate'; import {withSetUserStatus, withSuspendUser, withSetCommentStatus} from 'coral-framework/graphql/mutations'; -import {handleCommentStatusChange, handleCommentEdit} from '../../../graphql/utils'; +import {handleCommentStatusChange} from '../../../graphql/utils'; import {fetchSettings} from 'actions/settings'; import {updateAssets} from 'actions/assets'; @@ -44,16 +45,16 @@ class ModerationContainer extends Component { }, updateQuery: (prev, {subscriptionData: {data: {commentStatusChanged: comment}}}) => { const user = comment.status_history[comment.status_history.length - 1].assigned_by; - - const extraParams = this.props.auth.user.id === user.id + const notify = this.props.auth.user.id === user.id ? {} : { - notify: true, activeQueue: this.activeTab, + text: `${user.username} ${comment.status.toLowerCase()} comment "${truncate(comment.body, {lenght: 50})}"`, + anyQueue: false, }; return handleCommentStatusChange(prev, comment, { sort: this.props.moderation.sortOrder, - ...extraParams, + notify, }); }, }); @@ -63,14 +64,37 @@ class ModerationContainer extends Component { variables: { asset_id: this.props.data.variables.asset_id, }, - updateQuery: (prev, {subscriptionData: {data: {commentEdited}}}) => { - return handleCommentEdit(prev, commentEdited, { - activeQueue: this.activeTab, + updateQuery: (prev, {subscriptionData: {data: {commentEdited: comment}}}) => { + return handleCommentStatusChange(prev, comment, { + sort: this.props.moderation.sortOrder, + notify: { + activeQueue: this.activeTab, + text: `${comment.user.username} edited comment to "${truncate(comment.body, {lenght: 50})}"`, + anyQueue: false, + }, }); }, }); - this.subscriptions.push(sub1, sub2); + const sub3 = this.props.data.subscribeToMore({ + document: COMMENTS_FLAGGED_SUBSCRIPTION, + variables: { + asset_id: this.props.data.variables.asset_id, + }, + updateQuery: (prev, {subscriptionData: {data: {commentFlagged: comment}}}) => { + const user = comment.actions[comment.actions.length - 1].user; + return handleCommentStatusChange(prev, comment, { + sort: this.props.moderation.sortOrder, + notify: { + activeQueue: this.activeTab, + text: `${user.username} flagged comment "${truncate(comment.body, {lenght: 50})}"`, + anyQueue: true, + }, + }); + }, + }); + + this.subscriptions.push(sub1, sub2, sub3); } unsubscribe() { @@ -216,6 +240,15 @@ const COMMENTS_EDITED_SUBSCRIPTION = gql` ${Comment.fragments.comment} `; +const COMMENTS_FLAGGED_SUBSCRIPTION = gql` + subscription CommentFlagged($asset_id: ID){ + commentFlagged(asset_id: $asset_id){ + ...${getDefinitionName(Comment.fragments.comment)} + } + } + ${Comment.fragments.comment} +`; + const STATUS_CHANGED_SUBSCRIPTION = gql` subscription CommentStatusChanged($asset_id: ID){ commentStatusChanged(asset_id: $asset_id){ diff --git a/graph/mutators/action.js b/graph/mutators/action.js index 96bf091cf..19db33183 100644 --- a/graph/mutators/action.js +++ b/graph/mutators/action.js @@ -13,7 +13,16 @@ const {CREATE_ACTION, DELETE_ACTION} = require('../../perms/constants'); * @param {String} action_type type of the action * @return {Promise} resolves to the action created */ -const createAction = async ({user = {}}, {item_id, item_type, action_type, group_id, metadata = {}}) => { +const createAction = async ({user = {}, pubsub, loaders: {Comments}}, {item_id, item_type, action_type, group_id, metadata = {}}) => { + + let comment; + if (pubsub && item_type === 'COMMENTS') { + comment = await Comments.get.load(item_id); + if (!comment) { + throw new Error('Comment not found'); + } + } + let action = await ActionsService.insertUserAction({ item_id, item_type, @@ -29,6 +38,10 @@ const createAction = async ({user = {}}, {item_id, item_type, action_type, group await UsersService.setStatus(item_id, 'PENDING'); } + if (pubsub && comment) { + pubsub.publish('commentFlagged', comment); + } + return action; }; diff --git a/graph/resolvers/subscription.js b/graph/resolvers/subscription.js index cc98b19da..3c2130ff1 100644 --- a/graph/resolvers/subscription.js +++ b/graph/resolvers/subscription.js @@ -8,6 +8,9 @@ const Subscription = { commentStatusChanged(comment) { return comment; }, + commentFlagged(comment) { + return comment; + }, }; module.exports = Subscription; diff --git a/graph/subscriptions.js b/graph/subscriptions.js index 9b3df6b0e..494345319 100644 --- a/graph/subscriptions.js +++ b/graph/subscriptions.js @@ -12,7 +12,9 @@ const {deserializeUser} = require('../services/subscriptions'); const { SUBSCRIBE_COMMENT_STATUS, + SUBSCRIBE_COMMENT_FLAGS, SUBSCRIBE_ALL_COMMENT_EDITS, + SUBSCRIBE_ALL_COMMENT_ADDITIONS, } = require('../perms/constants'); /** @@ -27,7 +29,12 @@ const setupFunctions = plugins.get('server', 'setupFunctions').reduce((acc, {plu }, { commentAdded: (options, args) => ({ commentAdded: { - filter: (comment) => comment.asset_id === args.asset_id + filter: (comment, context) => { + if (!args.asset_id && (!context.user || !context.user.can(SUBSCRIBE_ALL_COMMENT_ADDITIONS))) { + return false; + } + return !args.asset_id || comment.asset_id === args.asset_id; + } }, }), commentEdited: (options, args) => ({ @@ -40,6 +47,16 @@ const setupFunctions = plugins.get('server', 'setupFunctions').reduce((acc, {plu } }, }), + commentFlagged: (options, args) => ({ + commentFlagged: { + filter: ({comment}, context) => { + if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_FLAGS)) { + return false; + } + return !args.asset_id || comment.asset_id === args.asset_id; + } + }, + }), commentStatusChanged: (options, args) => ({ commentStatusChanged: { filter: ({comment}, context) => { diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index d2c927995..948d0672f 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -948,12 +948,19 @@ type RootMutation { type Subscription { # Get an update whenever a comment was added. - commentAdded(asset_id: ID!): Comment + # `asset_id` is required except for users with the `ADMIN` or `MODERATOR` role. + commentAdded(asset_id: ID): Comment # Get an update whenever a comment was edited. + # `asset_id` is required except for users with the `ADMIN` or `MODERATOR` role. commentEdited(asset_id: ID): Comment + # Get an update whenever a comment was flagged. + # Requires the `ADMIN` or `MODERATOR` role. + commentFlagged(asset_id: ID): Comment + # Get an update whenever the status of a comment changed due to a moderator action. + # Requires the `ADMIN` or `MODERATOR` role. commentStatusChanged(asset_id: ID): Comment } diff --git a/perms/constants.js b/perms/constants.js index 37c5c3814..8f92c09a3 100644 --- a/perms/constants.js +++ b/perms/constants.js @@ -26,6 +26,7 @@ module.exports = { // subscriptions SUBSCRIBE_COMMENT_STATUS: 'SUBSCRIBE_COMMENT_STATUS', - SUBSCRIBE_ALL_COMMENT_FLAGS: 'SUBSCRIBE_ALL_COMMENT_FLAGS', + SUBSCRIBE_COMMENT_FLAGS: 'SUBSCRIBE_COMMENT_FLAGS', + SUBSCRIBE_ALL_COMMENT_ADDITIONS: 'SUBSCRIBE_ALL_COMMENT_ADDITIONS', SUBSCRIBE_ALL_COMMENT_EDITS: 'SUBSCRIBE_ALL_COMMENT_EDITS', }; diff --git a/perms/subscriptionReducer.js b/perms/subscriptionReducer.js index e2570b272..218443ead 100644 --- a/perms/subscriptionReducer.js +++ b/perms/subscriptionReducer.js @@ -3,11 +3,13 @@ const types = require('./constants'); module.exports = (user, perm) => { switch (perm) { + case types.SUBSCRIBE_COMMENT_FLAGS: + return check(user, ['ADMIN', 'MODERATOR']); case types.SUBSCRIBE_COMMENT_STATUS: return check(user, ['ADMIN', 'MODERATOR']); case types.SUBSCRIBE_ALL_COMMENT_EDITS: return check(user, ['ADMIN', 'MODERATOR']); - case types.SUBSCRIBE_ALL_COMMENT_FLAGS: + case types.SUBSCRIBE_ALL_COMMENT_ADDITIONS: return check(user, ['ADMIN', 'MODERATOR']); default: break;