diff --git a/client/coral-admin/src/graphql/utils.js b/client/coral-admin/src/graphql/utils.js index 407a4e53b..0b7384622 100644 --- a/client/coral-admin/src/graphql/utils.js +++ b/client/coral-admin/src/graphql/utils.js @@ -93,7 +93,7 @@ function showNotification(queue, comment, user) { notification.info(text); } -export function handleCommentStatusChange(root, comment, {sort, notify, user, activeQueue}) { +export function handleCommentStatusChange(root, comment, {sort, notify, activeQueue}) { let next = root; const nextQueues = getCommentQueues(comment); @@ -102,7 +102,8 @@ export function handleCommentStatusChange(root, comment, {sort, notify, user, ac if (notificationShown) { return; } - showNotification(...args); + const user = comment.status_history[comment.status_history.length - 1].assigned_by; + showNotification(...[...args, user]); notificationShown = true; }; @@ -111,13 +112,13 @@ export function handleCommentStatusChange(root, comment, {sort, notify, user, ac if (!queueHasComment(next, queue, comment.id)) { next = addCommentToQueue(next, queue, comment, sort); if (notify && activeQueue === queue && shouldCommentBeAdded(next, queue, comment, sort)) { - showNotificationOnce(queue, comment, user); + showNotificationOnce(queue, comment); } } } else if(queueHasComment(next, queue, comment.id)){ next = removeCommentFromQueue(next, queue, comment.id); if (notify && activeQueue === queue) { - showNotificationOnce(queue, comment, user); + showNotificationOnce(queue, comment); } } @@ -127,7 +128,7 @@ export function handleCommentStatusChange(root, comment, {sort, notify, user, ac && notify && activeQueue === queue ) { - showNotificationOnce(queue, comment, user); + showNotificationOnce(queue, comment); } // TODO: Flagged notification diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index 737afdac9..ede004c87 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -42,12 +42,13 @@ class ModerationContainer extends Component { variables: { asset_id: this.props.data.variables.asset_id, }, - updateQuery: (prev, {subscriptionData: {data: {commentStatusChanged: {user, comment}}}}) => { + 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 ? {} : { notify: true, - user, activeQueue: this.activeTab, }; return handleCommentStatusChange(prev, comment, { @@ -218,12 +219,14 @@ const COMMENTS_EDITED_SUBSCRIPTION = gql` const STATUS_CHANGED_SUBSCRIPTION = gql` subscription CommentStatusChanged($asset_id: ID){ commentStatusChanged(asset_id: $asset_id){ - user { - id - username - } - comment { - ...${getDefinitionName(Comment.fragments.comment)} + ...${getDefinitionName(Comment.fragments.comment)} + status_history { + type + created_at + assigned_by { + id + username + } } } } diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index d9dd47a9a..8d01ededf 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -346,6 +346,12 @@ const setStatus = async ({user, loaders: {Comments}, pubsub}, {id, status}) => { // adjust the affected user's karma in the next tick. process.nextTick(adjustKarma(Comments, id, status)); + if (pubsub) { + + // Publish the comment status change via the subscription. + pubsub.publish('commentStatusChanged', comment); + } + return comment; }; diff --git a/graph/resolvers/comment_status_history.js b/graph/resolvers/comment_status_history.js new file mode 100644 index 000000000..2e1676d90 --- /dev/null +++ b/graph/resolvers/comment_status_history.js @@ -0,0 +1,11 @@ +const {SEARCH_OTHER_USERS} = require('../../perms/constants'); + +const CommentStatusHistory = { + assigned_by({assigned_by}, _, {user, loaders: {Users}}) { + if (user && user.can(SEARCH_OTHER_USERS) && assigned_by != null) { + return Users.getByID.load(assigned_by); + } + } +}; + +module.exports = CommentStatusHistory; diff --git a/graph/resolvers/index.js b/graph/resolvers/index.js index f2707bcc8..850a2f15a 100644 --- a/graph/resolvers/index.js +++ b/graph/resolvers/index.js @@ -6,6 +6,7 @@ const Action = require('./action'); const AssetActionSummary = require('./asset_action_summary'); const Asset = require('./asset'); const Comment = require('./comment'); +const CommentStatusHistory = require('./comment_status_history'); const Date = require('./date'); const FlagActionSummary = require('./flag_action_summary'); const FlagAction = require('./flag_action'); @@ -31,6 +32,7 @@ let resolvers = { AssetActionSummary, Asset, Comment, + CommentStatusHistory, Date, FlagActionSummary, FlagAction, diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index ac2fd729d..f192405cc 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -31,18 +31,8 @@ const RootMutation = { stopIgnoringUser(_, {id}, {mutators: {User}}) { return wrapResponse(null)(User.stopIgnoringUser({id})); }, - setCommentStatus(_, {id, status}, {mutators: {Comment}, user, pubsub}) { - const response = Comment.setStatus({id, status}) - .then((comment) => { - if (pubsub) { - - // Publish the comment status change via the subscription. - pubsub.publish('commentStatusChanged', {user, comment}); - } - return Promise.resolve(comment); - }); - - return wrapResponse(null)(response); + setCommentStatus(_, {id, status}, {mutators: {Comment}}) { + return wrapResponse(null)(Comment.setStatus({id, status})); }, addTag(_, {tag}, {mutators: {Tag}}) { return wrapResponse(null)(Tag.add(tag)); diff --git a/graph/resolvers/subscription.js b/graph/resolvers/subscription.js index 8a989cc81..cc98b19da 100644 --- a/graph/resolvers/subscription.js +++ b/graph/resolvers/subscription.js @@ -5,8 +5,8 @@ const Subscription = { commentEdited(comment) { return comment; }, - commentStatusChanged(data) { - return data; + commentStatusChanged(comment) { + return comment; }, }; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 2bd7c0476..d2c927995 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -247,6 +247,12 @@ type EditInfo { editableUntil: Date } +type CommentStatusHistory { + type: COMMENT_STATUS! + created_at: Date! + assigned_by: User +} + # Comment is the base representation of user interaction in Talk. type Comment { @@ -286,6 +292,9 @@ type Comment { # The current status of a comment. status: COMMENT_STATUS! + # The status history of the comment. Requires the `ADMIN` or `MODERATOR` role. + status_history: [CommentStatusHistory!] + # The time when the comment was created created_at: Date! @@ -936,16 +945,16 @@ type RootMutation { ## Subscriptions ################################################################################ -# Response to ignoreUser mutation -type CommentStatusChangedUpdate { - user: User - comment: Comment -} - type Subscription { + + # Get an update whenever a comment was added. commentAdded(asset_id: ID!): Comment + + # Get an update whenever a comment was edited. commentEdited(asset_id: ID): Comment - commentStatusChanged(asset_id: ID): CommentStatusChangedUpdate + + # Get an update whenever the status of a comment changed due to a moderator action. + commentStatusChanged(asset_id: ID): Comment } ################################################################################ diff --git a/perms/constants.js b/perms/constants.js index 0b14932f3..37c5c3814 100644 --- a/perms/constants.js +++ b/perms/constants.js @@ -22,6 +22,7 @@ module.exports = { SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS: 'SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS', SEARCH_OTHERS_COMMENTS: 'SEARCH_OTHERS_COMMENTS', SEARCH_COMMENT_METRICS: 'SEARCH_COMMENT_METRICS', + SEARCH_COMMENT_STATUS_HISTORY: 'SEARCH_COMMENT_STATUS_HISTORY', // subscriptions SUBSCRIBE_COMMENT_STATUS: 'SUBSCRIBE_COMMENT_STATUS', diff --git a/perms/queryReducer.js b/perms/queryReducer.js index 0e5054788..2bee02110 100644 --- a/perms/queryReducer.js +++ b/perms/queryReducer.js @@ -15,6 +15,8 @@ module.exports = (user, perm) => { return check(user, ['ADMIN', 'MODERATOR']); case types.SEARCH_COMMENT_METRICS: return check(user, ['ADMIN', 'MODERATOR']); + case types.SEARCH_COMMENT_STATUS_HISTORY: + return check(user, ['ADMIN', 'MODERATOR']); default: break; }